How to transfer proxy admin ownership to a custom multisig wallet contract and upgrade proxy via a multisig?

Hi, I'm writing some tests for contract upgrades. After deploying the initial version of a contract I'd like to test upgrading it via my multisig wallet contract. I would expect to use the transferProxyAdminOwnerShip function for this. What I'm confused with is how to call the upgradeProxy function via a multiSig wallet? Normally the wallet requires two signatures in order to make a transaction.

:1234: Code to reproduce

For example, a pattern I use to make multiSig transactions is:

        const data = TestUpgradeableTokenContract.methods.mint(account3,'1000').encodeABI();
        await multiSigWalletInstance.submitTransaction(TestUpgradeableTokenInstance.address, 0, data, { from: account1 });
        const txId = await getTxIdFromMultiSigWallet(multiSigWalletInstance);
        await multiSigWalletInstance.confirmTransaction(txId, { from: account2 });

This will mint some tokens to account3 via a multisig.

I don't know how to upgrade a proxy contract in this way however. I couldn't find any tutorials so I've decided to make a post to do this.

Thanks!

:computer: Environment

Truffle v5.5.18 (core: 5.5.18)
Ganache v7.2.0
Solidity - 0.8.0
Node v16.9.1
Web3.js v1.5.3

To transfer proxy admin ownership, use admin.transferProxyAdminOwnership.

To upgrade after the proxy admin ownership has been transferred to a multisig:

  1. You can use the upgrades plugin to call prepareUpgrade which validates and deploys the new implementation contract. (This step can be done with any deployer address and does not need to come from the multisig.)

  2. Then after the new implementation contract has been deployed, you can use your multisig to call the ProxyAdmin's upgrade or upgradeAndCall, passing in the new implementation address.

Thanks @ericglau,

When I try to prepare my upgrade in local development network, there seems to be no deployment of the new implementation contract. When I then call the upgrade function using my multisig contract I receive an error message that the implementation contract hasn't been deployed.

3_prepare_contract_upgrade.ts:

import { prepareUpgrade } from '@openzeppelin/truffle-upgrades';
import { Network } from './types';

module.exports = (artifacts: Truffle.Artifacts) => {
    return async (deployer: Truffle.Deployer, network: Network, accounts: string[]) => {
        const TokenUpgradeable = artifacts.require('TokenUpgradeable');
        const TokenUpgradeableTest = artifacts.require('TokenUpgradeableTest');
        const TokenUpgradeableInstance = await TokenUpgradeable.deployed();
        await prepareUpgrade(TokenUpgradeableInstance.address, TokenUpgradeableTest as any, { deployer: deployer as any });
    };
};

4_upgrade_token_contract.ts:

import { admin } from '@openzeppelin/truffle-upgrades';
import { Network } from './types';
import { getTxIdFromMultiSigWallet } from '../test/lib/test-helpers';

module.exports = (artifacts: Truffle.Artifacts) => {
    return async (deployer: Truffle.Deployer, network: Network, accounts: string[]) => {
        const TokenUpgradeableTest = artifacts.require('TokenUpgradeableTest');
        const TokenUpgradeableTestInstance = await TokenUpgradeableTest.deployed();
        console.log(`TokenUpgradeableTestInstance address: ${TokenUpgradeableTestInstance.address}`);

        const MultiSigWallet = artifacts.require('MultiSigWallet');
        const multiSigWalletInstance = await MultiSigWallet.deployed();
        const adminInstance = await admin.getInstance();
        const data = await adminInstance.contract.methods.upgrade(TokenUpgradeableTestInstance.address).encodeABI();
        await multiSigWalletInstance.submitTransaction(adminInstance.address, 0, data, { from: accounts[0] });
        const txId = await getTxIdFromMultiSigWallet(multiSigWalletInstance);
        await multiSigWalletInstance.confirmTransaction(txId, { from: accounts[1] });
        console.log('Upgraded admin');
    };
};

I can however log out the output of prepareUpgrade and I'm given an address. What's confusing is that in the Truffle migrations log there is 0 gas spent and no information about any deployment of the new implementation contract.

Why am I getting conflicting information and how best to proceed?

If the same implementation was previously deployed on the network, prepareUpgrade would just reuse that address instead of deploying another copy.

Is your adminInstance an instance of ProxyAdmin? Note that when you call ProxyAdmin's upgrade, it should have two parameters: the proxy, and the new implementation address.

Yes, adminInstance is an instance of ProxyAdmin. That's worked perfectly now. Thanks!

1 Like