UUPS Upgradeable Contract - Transfer Ownership

I am working on a UUPS upgradeable contract that I need to transfer the ownership of to an admin.
I am implementing AccessControl on this smart contract.

I read that for upgradeable contracts, the owner is not msg.sender, but it is the ProxyAdmin.
Is this also true for UUPS upgradeable contracts?

If yes, how do I find this proxyAdmin address?
And how do I use this proxyAdmin address to grant the admin role to another user?

Thank you so much in advance for any help!

UUPS upgradeable contracts do not make use of a ProxyAdmin. A UUPS proxy controls who can upgrade it by the _authorizeUpgrade function that you need to override. You can use Wizard and select both UUPS and Roles (which uses AccessControl) for an example.

1 Like

Thank you so much for the reference!

I have authorize upgrade setup correctly.
This is how I set it up in the contract:

function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}

Then when I try to transfer the ownership of this role to a different signer like this:

const updateOwner = await myContract.connect(ADMIN_ROLE).grantRole(ADMIN_ROLE, other.address, {
        gasLimit: 300000
   await updateOwner.wait();

The address of my DEFAULT_ADMIN_ROLE is

ADMIN_ROLE = "0x0000000000000000000000000000000000000000000000000000000000000000";

And I end up with this error:

Error: invalid address (argument="address", value="0x0000000000000000000000000000000000000000000000000000000000000000", code=INVALID_ARGUMENT, version=address/5.5.0)  
reason: 'invalid address',
  argument: 'address',
  value: '0x0000000000000000000000000000000000000000000000000000000000000000'

Any thoughts on what I am doing wrong are hugely appreciated!! Thank you

Roles are not addresses. They are identifiers (of type bytes32). So you cannot do myContract.connect(ADMIN_ROLE) and you cannot "transfer" a role, but you can grant or revoke a role for an actual address. See https://docs.openzeppelin.com/contracts/4.x/api/access#AccessControl

I added to my contract:

bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

So would granting this role from an actual address, look like this?

 await myContract.grantRole(ADMIN_ROLE, other.address)

That looks right. In this case the account that you are using (the wallet that ethers.js is connected to) has role DEFAULT_ADMIN_ROLE which is the admin role for all roles, so it has permission to grant ADMIN_ROLE to other.address.

Reference from the above doc:

By default, the admin role for all roles is DEFAULT_ADMIN_ROLE , which means that only accounts with this role will be able to grant or revoke other roles. More complex role relationships can be created by using _setRoleAdmin.

Etherscan says that the wallet account that ethers.js is connected to does not have ADMIN_ACCESS.
Why would this be?

You need to set _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); in the initializer so that msg.sender has the rights to grant other roles.

Keep in mind there is a difference between DEFAULT_ADMIN_ROLE and the ADMIN_ROLE that you are trying to assign.

Thank you @ericglau for all the insight and feedback!

One further confirmation:
When a role is set in function _authorizeUpgrade in a smart contract, the address with that role automatically has the permissions to authorize an upgrade without any further grantRole post-deployment interactions in ethers.js. Correct?

To use more specific phrasing, if _authorizeUpgrade has a modifier onlyRole(CUSTOM_ROLE), then whichever addresses have the role CUSTOM_ROLE (whether that role was granted to the address in the initializer or granted post-deployment of the proxy) will be able to upgrade the proxy.

1 Like