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.
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!
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:
-
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.)
-
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