Cannot transfer ownership of UUPSUpgradeable Contract to Gnosis Safe

I’m unable to transfer ownership of a UUPSUpgradeable + OwnableUpgradeable to a Gnosis Safe per the tutorial here: OpenZeppelin Upgrades: Step by Step Tutorial for Hardhat by @abcoathup

:computer: Environment

"@openzeppelin/contracts": "^4.0.0",
"@openzeppelin/contracts-upgradeable": "^4.1.0",
"solc": "0.8.2",
"hardhat": "^2.0.8",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^2.1.2",
"@nomiclabs/hardhat-waffle": "^2.0.0",
"@opengsn/gsn": "^2.1.0",
"@openzeppelin/hardhat-upgrades": "^1.6.0",
"chai": "^4.2.0",
"ethereum-waffle": "^3.0.0",
"ethers": "^5.0.0",
"hardhat": "^2.0.8",

:memo:Details

What’s the recommended way to transfer ownership of of a UUPSUpgradeable Contract to a Gnosis Safe? The goal is to use Defender + Gnosis to upgrade this. The contract in question inherits from: Initializable, ERC2771ContextUpgradeable, UUPSUpgradeable, OwnableUpgradeable

:1234: Code to reproduce

When this code is run:

import { upgrades } from "hardhat";

const GNOSIS_SAFE = '0x9E2028cd1Fb68b908cF49F4743f2E52d3c0A7e9b';

async function main () {
  console.log('Transferring ownership of ProxyAdmin...');
  await upgrades.admin.transferProxyAdminOwnership(GNOSIS_SAFE);
   console.log('Transferred ownership of ProxyAdmin to:', GNOSIS_SAFE);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);
  });

Fails with error:

Transferring ownership of ProxyAdmin...
Error: No ProxyAdmin was found in the network manifest
    at getManifestAdmin (.../node_modules/@openzeppelin/hardhat-upgrades/src/admin.ts:57:11)
    at Object.transferProxyAdminOwnership (...node_modules/@openzeppelin/hardhat-upgrades/src/admin.ts:29:19)
    at main (.../scripts/transfer-ownership.ts:7:3)

This makes sense because there is no admin entry in the rinkeby.json config. If there was one, we would get the warning:

Warning: A proxy admin was previously deployed on this network

    This is not natively used with the current kind of proxy ('uups').
    Changes to the admin will have no effect on this new proxy.

For context, the script was deployed with:

import { ethers, upgrades } from "hardhat";

async function main() {
  const RINKEBY_TRUSTED_FORWARDER = '0x956868751Cc565507B3B58E53a6f9f41B56bed74'  
  const MyContractFactory = await ethers.getContractFactory("MyContract");
  const myContract = await upgrades.deployProxy(MyContractFactory, [RINKEBY_TRUSTED_FORWARDER], {kind: 'uups'});
  console.log("Deployed to:", myContract.address);
}


main()
  .then(() => process.exit(0))
  .catch(error => {
    console.error(error);
    process.exit(1);

So what’s the recommended way to do the upgrade, while preserving the right configuration settings here for future upgrades?

Hi, so do you follow the tutorial step by step?
I can not find the pacakge in your package.json : @openzeppelin/hardhat-upgrades, so did you install it by npm install --save-dev @openzeppelin/hardhat-upgrades

Yes, I do have hardhat-upgrades installed, updated the post to reflect the dev dependecies that are installed.

So have you also deployed the contracts with the

await upgrades.deployProxy(Your_Contract_Factory)
1 Like

Yes I did.

It generated the rinkeby.json file and the contract exists. However the rinkeby.json file does not have an “admin” item because uups does not require or use an admin.

But it appears that the call to transferProxyAdminOwnership requires an admin entry.

Hi @varunsrin. The problem is that UUPS contracts do not have a ProxyAdmin, so the tutorial doesn’t apply in the same way. Sorry about the confusion with that, we have to add a comment about this.

With UUPS proxies as deployed by the plugins, the proxy owner will be the account that deployed the contract. So if you want to transfer ownership you should take an instance of your contract and call the transferOwnership function directly.

4 Likes

Hi @frangio you have an example for doing this? Would appreciate any help. Thanks!

@trmrz In your hardhat script, you can do something like this (replace <deployed proxy address> with the address of the proxy that you have previously deployed):

  const MyContractFactory = await ethers.getContractFactory("MyContract");
  const contract = MyContractFactory.attach('<deployed proxy address>');

  console.log("Previous owner:", await contract.owner());
  const tx = await contract.transferOwnership(GNOSIS_SAFE);
  await tx.wait();
  console.log("New owner:", await contract.owner());

@ericglau Will doing this supposedly show the GnosisSafe ownership on Defender? Because I did this and it's not showing the GnosisSafe as the owner on Defender unlike in the tutorials. I thought I had previously made a mistake in my code. Thanks so much for all the help!

@trmrz If you go to the contract in Defender, click "New Proposal" -> "Admin action", you should see the updated owner() in the "Contract state" section on the right side.

Hi @ericglau . I have implemented UUPS proxy contract . I than transfer the ownership from the contract transferOwnership function to Gnosis safe created in Defender. After using the procedure described in the guide below:

I try to create an upgrade proposal . It is created but when i try to open it gives error. I have changed the contract address. And the contract shows the address of gnosis safe in owner. The error thrown is mentioned below

"Something went wrong when trying to load this proposal (Cannot determine upgrades owner). Please try again or contact us at defender-support@openzeppelin.com."

I have tried deploy contract on different blockchains as well but the result is the same.



Capture3

I've been using this code for what I am trying to do (not the same exact thing as the OP but I'm trying to upgrade a contract and have this code in an async function) but I get an error that is relevant. I'll make a separate topic if you think I should but here it goes. Basically, it comes back with "TypeError: contract.owner is not a function". Using Ethers: 5.0.0, Node: 16.14.0, Solidity: ^0.8.0, HardHat: 2.9.1. I'm pretty sure my config file has everything. Also tested on a Solidity 0.5.12 file.

I commented out the lines with contract.owner() and it brought back an error "TypeError: contract.transferOwnership is not a function", which shows I'm not getting any of the Ownable.sol functions. Anyway to fix this? I know I just upgraded my @nomiclabs/hardhat-ethers. Let me know if there's anything I should be importing extra that I haven't just to be sure or what I should be doing here. Thanks.

@aamazie Can you share what your script looks like?


// scripts/upgrade_box.js
const { ethers, upgrades } = require('hardhat');

async function main () {
  const MyContractFactory = await ethers.getContractFactory("BoxV2");
  const contract = MyContractFactory.attach('0x4bf010f1b9beDA5450a8dD702ED602A104ff65EE');

  console.log("Previous owner:", await contract.owner());
  const tx = await contract.transferOwnership('0x70997970c51812dc3a010c7d01b50e0d17dc79c8');
  await tx.wait();
  console.log("New owner:", await contract.owner());

  const BoxV2 = await ethers.getContractFactory('BoxV2');
  console.log('Upgrading Box...');
  await upgrades.upgradeProxy('0x4bf010f1b9beDA5450a8dD702ED602A104ff65EE', BoxV2);
  console.log('Box upgraded');
}

main();

Hi @aamazie, it looks to me like that could be due to one of the following problems:

  • Wrong address or wrong network being used. Ensure that your address 0x4bf010f1b9beDA5450a8dD702ED602A104ff65EE is the right address of the existing proxy on the network that you are using. Also, your script looks like it is upgrading BoxV2 to the same BoxV2 -- is that intentional?
  • Ensure that your contract is inheriting OwnableUpgradeable (since you are using it with proxies).

If you are still encountering issues, could you share an example of your contracts and the exact command that you are running?

Hi @frangio,

I have question regarding this. So, in the transparent proxy, there is a ProxyAdmin contract that is basically the admin of the Proxy. Whenever you change the admin from ProxyAdmin, to let's say deployer.address, then the deployer will be the new admin for the proxy. This is fine. However, the Proxy admin should not be able to call the implementation methods, right? Well, if we are using UUPS proxy, even the deployer is able to access the implementation methods, which makes the case totally different.

Is there anyway that I can achieve the same restriction for the UUPS proxies?

Thank is advance and BR,
Vitomir

Hi @Vitomir2,

I have question regarding this. So, in the transparent proxy, there is a ProxyAdmin contract that is basically the admin of the Proxy. Whenever you change the admin from ProxyAdmin, to let's say deployer.address, then the deployer will be the new admin for the proxy. This is fine. However, the Proxy admin should not be able to call the implementation methods, right? Well, if we are using UUPS proxy, even the deployer is able to access the implementation methods, which makes the case totally different.

These look correct.

Is there anyway that I can achieve the same restriction for the UUPS proxies?

It could be possible to do something like the transparent proxy's _beforeFallback in your own customized extension of ERC1967Proxy, and have some restriction on who the sender is.

But note that whichever account (e.g. the owner) that you want to do the upgrade must be able to call upgradeTo through the proxy (and that upgradeTo function is defined in the implementation, so it must go through the fallback function). Therefore, by definition, you cannot prevent that same account from accessing the fallback function. I think the only alternative is to use different roles and put a role-based restriction in _beforeFallback.

1 Like

Hi @ericglau,

Thank you a lot for the suggestion and the quick response.