Help debugging transaction from proxy contract

Hey everyone, I have a transaction that didn’t quite work the way I expected and am looking for some help debugging it.

So there is a Factory contract deployed using the OpenZeppelin CLI here. This factory deploys minimal proxy instances of this Forwarder logic contract.

The Forwarder logic contract is upgradeable from the OpenZeppelin CLI, so when calling a function on a deployed minimal proxy, I’d expect that to be sent via delegatecall to the Forwarder proxy, which then delegatecalls to the Forwarder implementation.

In this transaction we created a minimal Forwarder proxy, which we can see was deployed to 0xdcc6C61B04374cFA08762EE5496672C9AffD0f93.

I sent a few Dai to the minimal Forwarder proxy, and in this transaction called the mintAndSendChai() method on that contract, which should convert the Dai to Chai and send it to the owner of the minimal proxy.

Like mentioned above, I’d expect there to be two delegate calls to reach the implementation contract, but it seems that never happened. From the Parity trace the first delegate call to the Forwarder proxy was made, but the second delegatecall was sent to the zero address?

I’m not sure what exactly is happening here. Any help is appreciated!

1 Like

Hi @msolomon4,

If I understand your setup correctly, what appears to be happening is the following:

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

Ah, I think I see what you mean—so because the minimal proxy delegatecalls into the OpenZeppelin proxy, it’s looking for the implementation address within the context of the minimal proxy’s storage, not the OpenZeppelin proxy storage.

Is there a way to workaround this such that a minimal proxy can point to an upgradeable contract?

1 Like

Hi @msolomon4,

Yes. It is a double delegate call.

If I understand your setup.
There is a factory creating minimal proxies, pointing to an OpenZeppelin upgradeable contract.

Which doesn’t work because of the double delegate call.

It depends what you want to achieve. Some possible options:

  1. The factory could create minimal proxies to logic contracts, but the logic contracts won’t be upgradeable.
  2. The factory could create upgradeable proxies to logic contracts, but upgrading would need to be done for each proxy.
  3. The factory could be upgradeable, creating minimal proxies to logic contracts. The factory could be upgraded, so that future minimal proxies used a new logic contract but the existing contracts would be unchanged.

I am not sure if there is a way to do what you were planning. Perhaps someone in the community could advise.

@msolomon4 if I understand correctly, you want to have multiple instances of a contract, which you can upgrade all in a single transaction. Unfortunately, the SDK does not support that use case (yet). As @abcoathup pointed out, any solution that relies on a double DELEGATECALL would not work.

I’d like to understand what’s the rationale for using the double indirection in your use case. Is it because regular upgradeable proxies are too expensive (in gas terms) vs a minimal proxy? Is it because you want centralized control of upgrades for all instances? Or because you want the upgrade of all instances to be doable atomically in a single tx?

In the meantime, I’d suggest taking a look at Dharma’s upgrade pattern using Beacon contracts, developed by 0age. Do you think something like this would suit your needs?

1 Like

@spalladino Since each user has their own contract, the main reason for originally going the minimal proxy route was to reduce the deployment gas costs. The ability to upgrade all instances with one tx was a secondary reason.

Given that these are pass-through contracts that won’t actually store funds (they convert and forward tokens to an EOA), and likely won’t be upgraded frequently, the workarounds suggested by @abcoathup are sufficient. The third one in particular was originally how I planned to work around this.

The beacon pattern would certainly suit these needs, and is pretty cool approach. I’d love to see that implemented into the SDK.

1 Like

Thanks for the reply, Matt. It’s helpful for us to know that deployment gas costs are a concern. We will probably be addressing those in 3.0.