How to execute "upgrade" on proxy admin

Hello,

I have questions about how to manually execute the upgrade action on the ProxyAdmin contract that gets deployed when I call upgrades.deployProxy via the buidler library.

I read through and followed the steps laid out in OpenZeppelin Buidler Upgrades: Step by Step Tutorial and was able to do it all successfully.

Below is the abbreviated sequence of events that I hope to achieve in a test:

Box = await ethers.getContractFactory("Box");
box = await upgrades.deployProxy(Box, [42], {initializer: 'store'});

bobAddress = await bob.getAddress()
await upgrades.admin.transferProxyAdminOwnership(bobAddress);

const BoxV2 = await ethers.getContractFactory("BoxV2");
const boxV2Address = await upgrades.prepareUpgrade(box.address, BoxV2);

await box.connect(bob).upgrade(box.address, boxV2Address

I already tried the last line and it failed as it said the upgrade method did not exist on the box contract. The challenge Im facing is finding the address of the ProxyAdmin contract so that, as bob, i can manually upgrade the box proxy to point to thew new V2 box implementation.

I dug into the open zeppelin node_modules and saw that when upgrades.upgradeProxy(box.address, BoxV2) is called the box.address is loaded into a ProxyAdmin contract and then upgrade is ran on it.

From my understanding, box.address, is the proxy address and its confusing why i cant use it to execute upgrade manually.

Thank you for any help.

Regards,
Jon-Eric Cook

2 Likes

Hi @jonericcook,

Welcome to the community forum :wave:

Thanks for posting here.

I don’t know if there is a way currently to access the ProxyAdmin contract in a test. To do the test that you want it feels like we need something like an admin.upgradeProxy or to be able to retrieve the address of the ProxyAdmin so that we can then interact with the contract.

I will need to come back to you on what is currently possible and if not create an issue.

Can you share with us why you're interested in running the upgrade manually instead of using the upgradeProxy function that we ship?


Yes, it's the same address, but box is an instance of the Box contract abstraction, so it only exposes Box's functions. If you want to use the proxy functions you need an instance of a proxy contract abstraction. This is not something that we support out of the box. You can manually get an Ethers.js ProxyFactory (the one that the plugins use internally) using the function getProxyFactory exported from @openzeppelin/buidler-upgrades/dist/proxy-factory, and attach that to box.address.

That said, since you're using admin.transferProxyAdminOwnership you would not be able to access the upgrade function on the proxy itself, you have to go through the proxy admin. Now this is a bit of an issue because we don't expose the proxy admin address, or a proxy admin instance, so at the moment the only way for you to get this instance is by reading the proxy admin address from .openzeppelin/<network>.json, and potentially using getProxyAdminFactory similarly to the previous paragraph.

I realize that this is cumbersome. We should be exposing an instance of the ProxyAdmin as part of the upgrades.admin module. I'll open an issue about this.

Now, if you intended to get rid of the ProxyAdmin, you should be using admin.changeAdminForProxy. Doing that you would be able to invoke upgrade directly on the proxy.

1 Like

Take a look at the docs we recently published for TransparentUpgradeableProxy in Contracts, which is the same contract the plugins are using for proxies, as well as ProxyAdmin.


The issue I created based on my previous message:

1 Like

The main goal is as follows:

We have two smart contracts

  • Message.sol (this is an upgradable smart contract with a proxy and all that)
  • Voter.sol (this is NOT an upgradable smart contract)

When Message.sol is deployed it is deployed with a proxy and all that. Voter.sol is deployed as a normal smart contract.

After Message.sol is deployed, proxy admin control is given to Voter.sol smart contract’s address.

Voter.sol has a method called execute that accepts the following parameters

address target, uint value, string memory signature, bytes memory data

so that those parameters can be used to like this

(bool success, bytes memory returnData) = target.call.value(value)(callData);

The idea is as follows: target would be the proxy address of the Message.sol contract, value would be 0, signature would be upgrade(TransparentUpgradeableProxy,address) and data would be callData (see below)

Using ethereumjs-abi library do the following for callData

const callData = abi.rawEncode(['address', 'address'], [message.address, messageV2Address])

The above is assuming message.address is the address of the Message.sol proxy contract.

Essentially, the goal is to be able to make a smart contract proxy admin of another smart contract so that it can upgrade it after some certain kind of logic is satisfied.

The idea is that someone can launch a new version of Message.sol by calling upgrades.prepareUpgrade and then pass the address of the new implementation to the execute action in Votes.sol.

So i guess im trying to figure out what is the proxy address of an upgradable smart contract and what is the correct function signature to call to upgrade the implementation said proxy points at.

2 Likes

If you want Voter to be the direct admin of the proxy, you should use upgrades.admin.changeAdminForProxy.

The above is assuming message.address is the address of the Message.sol proxy contract.

This is correct!

The correct function signature if the voter is the direct admin, is this one: upgradeTo(address).

You should make sure you understand the transparent proxy pattern and the way ProxyAdmin is used. If you use changeAdminForProxy as I suggested above, you don't need to be concerned with ProxyAdmin.

If you use transferProxyAdminOwnership (as in your initial post), the voter will need to send a transaction to the proxy admin contract (this is not the proxy contract), and in that case the function signature will be this one: upgrade(address,address).

Apologies while we're still figuring out how to structure our documentation and how to communicate these concepts best. Feel free to ask me anything.

1 Like

Thank you @frangio for the reply. Using await upgrades.admin.changeProxyAdmin(message.address, voter.address) and then using the signature upgradeTo(address) did the trick. I was able to upgrade which implementation the proxy pointed at.

If doing changeProxyAdmin accomplishes what I need, what should I do with transferProxyAdminOwnership?

If I want to give all admin / proxy control over to Voter, would I do
await upgrades.admin.changeProxyAdmin(message.address, voter.ddress)
await upgrades.admin.transferProxyAdminOwnership(voter.address)

I know when the deployProxy is called the first account in ethers.getSigners() is used as the admin of the proxies. Because of this, if I deploy N number of upgradable contracts, I would only need call await upgrades.admin.changeProxyAdmin(proxyAddressX, bobAddress) N times. So if I had Message.sol, Notes.sol, Stickies.sol that were all upgradable I would have to do

await upgrades.admin.changeProxyAdmin(message.address, voter.ddress)
await upgrades.admin.changeProxyAdmin(notes.address, voter.ddress)
await upgrades.admin.changeProxyAdmin(stickies.address, voter.ddress)
await upgrades.admin.transferProxyAdminOwnership(voter.address)

1 Like

If you use changeProxyAdmin, you have no need for transferProxyAdminOwnership.

I made these drawings which will hopefully make this clearer.

2 Likes

Yes, thank you, the above diagram helps clear some things up. So I guess what I’m doing is making a new proxy admin smart contract. I have a simple upgrade action in the Voter smart contract.

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

interface Proxy {
  function upgradeTo(address newImplementation) external;
}

contract Voter {
  function upgrade(address proxyAddress, address implementationAddress) public {
    require(votes >= 100, "not enough votes to upgrade");
    Proxy proxy = Proxy(proxyAddress);
    proxy.upgradeTo(implementationAddress);
  }
}

The votes counter logic is left out for brevity.

So all in all, I did

await upgrades.admin.changeProxyAdmin(message.address, voter.ddress)
await upgrades.admin.changeProxyAdmin(notes.address, voter.ddress)
await upgrades.admin.changeProxyAdmin(stickies.address, voter.ddress)

and then when certain the votes threshold is met, Voter.upgrade can be called to upgrade the proxy.

1 Like