How to upgrade contracts programmatically?

:computer: Environment

truffle 5.0.38
@openzeppelin/cli 2.5.3

:memo:Details

Hi there, I have a question, I have a project set up (oz+truffle) already and now I’m trying to write a script to upgrade my contracts programmatically. I’ve found this example here https://docs.openzeppelin.com/upgrades/2.8/#usage but it’s working for me. In my case I already have my upgradable contract deployed already and I want to change the implementation (keeping the existing proxy/address). How is the best way to do it properly?

Thanks!

1 Like

I was tackling a similar problem about a month ago…

Did you do this using openzeppelin cli or what?

1 Like

I’m running it programmatically from a truffle script, the code is essentially this:

const { ZWeb3, Contracts, SimpleProject } = require('@openzeppelin/upgrades');

const owner = "...";

module.exports = async (callback) => {
  // Initialize a web3 provider
  ZWeb3.initialize(web3.currentProvider);

  // Load the contract
  const MyContract = Contracts.getFromLocal('MyContract');

  // Instantiate a project
  const myProject = new SimpleProject('MyProject', {
    from: await ZWeb3.defaultAccount()
  });

  // Create a proxy for the contract
  const proxy = await myProject.createProxy(MyContract);

  // Make a change on the contract, and compile it
  const MyContractUpgraded = Contracts.getFromLocal('MyContractUpgraded');
  myProject.upgradeProxy(proxy, MyContractUpgraded, { initMethod: 'initialize', initArgs: [owner] });


  callback();
};
1 Like

Hi @marcelomorgado,

I’m very sorry about the documentation and the non-working example in the usage section of the README.

Please see the following from @frangio

From: How to create proxies programmatically during network congestion?

I’m really sorry about the lack of documentation around using upgrades programmatically from JavaScript. The existing JavaScript interface kind of evolved as the internals of the CLI, and was not optimized to be a user-facing API. This is definitely a weak point of the library, which is why we’re currently working on a redesign, announced in the Q2 Roadmap. Unfortunately I can’t share a timeline for it yet, but the project is likely going to extend into Q3.

If you’re in a position in which you need to use the current library, perhaps you’d find the example in the repo useful.

I will have to come back to you with a working example of using a SimpleProject.

Hey @marcelomorgado, can you try the following instead?

  const myProject = new SimpleProject('MyProject', null, {
    from: await ZWeb3.defaultAccount()
  }); 
1 Like

Hi @marcelomorgado,

Using the contracts from examples/upgrades-library we can do the following using SimpleProject:

Note: With Simple Project we don’t have a ProxyAdmin contract, so the creator is the admin of the proxy, and all other interaction with the proxy should be made using a non-admin account.

const { ZWeb3, Contracts, SimpleProject } = require('@openzeppelin/upgrades');

async function main() {
  // Initialize a web3 provider
  ZWeb3.initialize("http://localhost:8545");

  // Load the contract
  const MyContract = Contracts.getFromLocal('MyContractV0');

  const accounts = await ZWeb3.eth.getAccounts();

  // Instantiate a project
  const myProject = new SimpleProject('MyProject', null, {
    from: accounts[1]
  });

  // Create a proxy for the contract
  const instance = await myProject.createProxy(MyContract, { initMethod: 'initialize', initArgs: [42] });
  console.log(`Contract created at ${instance.options.address}`);

  const initialValue = await instance.methods.value().call();
  console.log(`Initial value is ${initialValue.toString()}`);

  const MyContractUpgraded = Contracts.getFromLocal('MyContractV1');
  const instanceV1 = await myProject.upgradeProxy(instance.options.address, MyContractUpgraded);

  await instanceV1.methods.add(10).send({ from: accounts[0], gas: 1e5, gasPrice: 1e9 });
  const newValue = await instanceV1.methods.value().call();
  console.log(`Updated value is ${newValue.toString()}`);
}

main();

Hi, sorry for the delay.
Thank you @abcoathup and @spalladino for the responses.

I’ve tried using the codes above but they didn’t work. I’ll try to give more details about what I’m trying to do.
The repo that I’m working on uses OZ cli for contracts deployment/upgrade. I want to write a code to upgrade already deployed contracts programatically, something like that:
Let’s say that I’ve just change the MyContract.sol code and want to deploy that and then change its proxy implementation address to point to the new logic address.

  1. Deploy the new smart contract implementation/logic
  2. Call the proxy contract to change the implementation address

That’s what I’m trying to do. Seems that the code above are always deploying a new proxy contract instead of upgrade the existing one.
Note: I’ve tried to used SampleProject and ProxyAdminProject but with the same behavior.

Thanks again

I see, and yeah, this approach will not work for you out of the box. The upgrades js library is just a library which does not interact with the files in the .openzeppelin folder, which track your deployed contracts. From the docs, emphasis mine:

The CLI does not just manage contract upgrades, but also compilation, interaction, and source code verification. The Upgrades library only takes care of creating and upgrading. The library also does not keep track of the contracts you have already deployed, nor runs any initializer or storage layout validations, as the CLI does. Nevertheless, these capabilities may be added to the Upgrades library in the near future.

Now, if you do want to programatically upgrade the contracts you have deployed via the CLI, probably the best option is to directly call the CLI from your code. You can use shelljs or child_process to call the CLI via your js code directly. You should set the OPENZEPPELIN_NON_INTERACTIVE env flag to prevent the CLI from making any interactive prompts while running on a script. Also, when running non-interactively, you’ll have to manually call oz compile and oz push before every oz upgrade. The push command takes care of deploying your implementation contracts to the network. It is handled automatically for you when running the commands interactively, but not on a script.

There is another alternative which is importing the CLI as a dependency into your code, and use the code from the scripts folder as an entry point. However, this requires you to do some initialization beforehand, and it is not documented, so I would not recommend it.

3 Likes