How can I get a deployed proxy contract's implementation address from inside solidity?

Hi,
How can I get a deployed proxy contract’s implementation address from inside solidity?
I wanna get this state from any contract really.
I only found this method: https://docs.openzeppelin.com/sdk/2.5/api/upgrades#BaseAdminUpgradeabilityProxy-implementation--

But it is not what I am looking for, due to ifAdmin modifier.

1 Like

Hi @gitpusha,

You can get the implementation address from the ProxyAdmin.

I am curious on why you need this inside Solidity, can you share more details?

2 Likes

Hi @abcoathup - thanks for your answer.
I will check how far that method gets me.

Ideally, though, I would like to be able to query the implementation address on the proxy that wraps the forwards calls to it.

It’s good you ask. I left the rationale out in my first post, due to time constraints. But it probably makes sense to explain it. To sum it up:

  1. our protocol uses userProxies (basically smart contracts that are meant to represent an end-user (EOA or not) in our protocol).
  2. these userProxies make delegatecalls to load code from other deployed contracts - we call the latter: actions.
  3. the action code loaded via delegatecall is supposed to be run inside the userProxy state context.
  4. If I oz create such an action with the OZ SDK the address our userProxy ends up delegatecalling into is the oz Proxy and NOT the action.
  5. Here, we get an error, where the transaction does not revert but somehow the callchain ends too early with a weird call to what looks like address(0).
  6. This error does not appear when we use the action implementation address for our userProxy -> action delegatecall - here we load the code from the action into the userProxy and it runs as intended.
  7. Maybe the ox proxy pattern does not work here because we are looking for a function selector that is supposed to be on the action implementation NOT the oz proxy that wraps the action? So it is like 2 consecutive delegatecalls. The code should actually not be run in the oz proxy though, since the actions are stateless anyway.
  8. Maybe the oz SDK is not designed with such a use case in mind? But we still would like to use the oz SDK here too, to benefit from its well audited upgradeability feature.

The reason why I want a function that returns the implementation address on the oz Proxy is that now I can let our userProxies consume the oz Proxy address and just get the current action implementation address with 1 call to the Proxy => and we dont have the double-delegatecall problem any more.

So basically, this would allow me to use (or maybe abuse?) the oz Proxy as an updateable registry that points me to my latest implementation. And not to use it to run code inside the oz proxy state. Instead, we want to run this code inside our userProxy state here.

I could easily write my own code to do this. But I’d much rather use the audited oz code instead and of course keep using the oz cli for this aspect of our protocol, too.

1 Like

I would also be interested in what contract and architecture the oz create --minimal uses/deploys:

Could it be that this is the kind of stateless/non-storage upgradeability I am looking for?

1 Like

I guess I am looking for a upgradeability proxy that doesnt care about storage persistance but only about implementation code upgrades. It should forward all incoming payloads to its current implementation via a call not delegatecall. Tbh the only difference to the standard OZ SDK would be to replace the call inside the proxy with a delegatecall - and some semantics like you dont need to initialize, each implementation initializes itself or is simply purely stateless.

1 Like

Hi @gitpusha,

The ZeppelinOS 2.4.0 released announcement covers EIP1167 minimal proxies:

Cheap EIP1167 minimal proxies

We have added experimental support for EIP1167 minimal proxies . These proxies rely on the same delegatecall pattern as the usual ZeppelinOS proxies, but have two main differences:

  • They cannot be upgraded to a different version of the code
  • They are incredibly cheap to deploy: about 1/10th of the standard proxy!

These features make minimal proxies an ideal choice when you want to spawn many copies of the same contract, without worrying about maintaining them in the future. They also play nicely with EVM packages: since the package developer pays the deployment cost of the logic contracts, you only need to pay for the minimal proxies.

You can try deploying one of these proxies by adding the --minimal flag when running zos create . Remember that these proxies will not be upgradeable to a different version of the code: the reduced deployment gas fee comes at a cost!

1 Like

Wow @gitpusha fascinating use case! A question, with such a use case, have you considered creating your upgradable proxies directly within solidity? That way you could keep track of your contracts and implementations directly on chain?

We have some examples of creating instance from within solidity.

2 Likes

Thanks so much for sharing the use case, @gitpusha! Indeed, it would seem like the issue is with the double delegate call, as you say - that’s why you are seeing calls to the zero address. Assuming a delegatecall chain from UserProxy to OzProxy to Action:

1- UserProxy delegatecalls into OzProxy
2- OzProxy loads its implementation address from storage in a special slot
3- But the current state (including storage) is that of the original UserProxy
4- The special implementation slot is empty, so the implementation address loaded is zero
5- OzProxy delegatecalls into zero

My suggestion here would be to just build a registry that points to the latest version of each Action, and not rely on OzProxies at all. This way, when a user wants to execute a given action, you just query this registry for the address of the latest version of the action contract, and execute it.

The SDK does provide support for this: if you oz publish your project, it will create a set of contracts that act as a registry. Whenever you make a change to your contracts (ie your actions,) you can oz push to upload and register the latest versions. You can even use oz bump to track multiple versions of your actions, and oz freeze to guarantee that a version will be unchanged.

You can then inject the App main contract to every UserProxy, and use App#getImplementation to get the latest version of an action by name.

Do you think sth like this would work?

2 Likes

Hi @Dennison. Thanks! Indeed, I have thought about that, but I maybe wrongly presumed that, if I write my own proxy factory etc. it would not be compatible with OZ SDK and cli.
I would want the solution to come out of the box with the oz SDK.
But I think what I am looking for is at odds with the current design goal of the oz SDK, which I would loosely define as upgradeability with storage persistance.

I am looking for a more minimal thing where you just wanna load code via delegatcall from upgradeable implementations. I guess that would imply that the upgradeabilityProxy just really is a registry not a delegatecall forwarder .

I know how I would implement this by myself. It’s quite easy. But since I am already using the oz sdk I was hoping I could maybe somehow get it to behave this way. But at this point it looks like I am forcing the sdk to do something it is not meant to do.

1 Like

Hi @spalladino - yes that delegatecall chain is the problem.

Regarding your suggested approach, it sounds interesting and I will try it out.

EDIT: Had a closer look at it. It is really great information and I really like the design. I think this could work well for our use case. BUT, we are currently moving away from upgradeability, to prepare our mainnet launch. I really enjoy using the current OZ SDK, but we had a decision in our team that for our first launch we cannot have upgradeability, due to the governance complexity involved.

I just wish there was a second OZ SDK similar to this one, just for non-upgradeable frameworks. I mean being able to oz compile oz deploy and oz call oz send-tx also for non-upgradeable contracts too would be so great.

1 Like