Guide: Full start-to-finish OpenZeppelin workflow for CLI deployment and upgrades

Before making this post, I was unsure what a complete start-to-finish workflow with the OpenZeppelin CLI would like like.

Thanks to @spalladino—a good chunk of the text in this guide was copied directly from his replies to my questions—I was able to put together this workflow in hopes that it will help anyone else with the same questions. It covers the steps for each stage of the development process, as well as some info on some unused commands and where those fit in.

Setup, Development, and Testing

  1. Create and initialize your project with npx oz init projectName

  2. Link the OpenZeppelin contracts that will be used as dependencies with npx oz link @openzeppelin/contracts-ethereum-package

    • This installs the above package and loads the addresses of the implementation contracts for pre-deployed contracts from @openzeppelin/contracts-ethereum-package. This means that, if you create an upgradeable ERC20, you’ll be reusing bytecode previously deployed by us, so you save time and gas.
    • Check out this article to learn more about implementation contracts and proxy instances if the concept is confusing.

At this point we can develop and test our contracts, and just generally play around with the CLI to get a feel for things.

Deployment

  1. If you did any test deployments (e.g. perhaps you were testing the upgrade process) you may want to reset the project before continuing on to the “real” deployment. This can be done with the steps from this forum post, which are summarized below. Note: The CLI does a good job at keeping stuff from different networks compartmentalized. You should only need to nuke your current settings in this way if you want to deploy from scratch on the same network. If you are moving from development to testnet, or testnet to mainnet, you don’t need to worry about this.

    1. Delete the build directory
    2. Delete the applicable <network>.json file(s)
    3. Remove all contracts from project.json using npx oz remove contractName1 contractName2
  2. Compile all contracts with desired Solidity version, e.g. npx oz compile --solc-version 0.5.12

  3. Deploy each contract with npx oz create. During these step be sure to also call the appropriate initialization function on each contract.

    • The create command also adds the contracts to project.json
  4. Verify the deployed contracts on Etherscan/Etherchain with npx oz verify.

  5. To reduce the risk of malicious upgrades you may want to change the upgradeability admin for all contracts to be a multisig or a hardware wallet. We can do this with npx oz set-admin.

At this point deployment is completed, and our contract/app can be used on whichever network we deployed to.

Upgrade Contracts

Now our contracts have been live for some time and we want to upgrade one or more of them.

  1. If you changed the admin to a hardware wallet or other address in step 5 above, you may now want to change it back to perform the upgrade with the CLI

    • If all contracts are controlled by the same admin address, one way to do this is by connecting your hardware wallet via MyCrypto and sending the transaction from there. You can call the transferOwnership method of the contract given by the proxyAdmin.address field of <network.json>.
    • If they contracts have different upgradeability admin addresses, you send the transaction to the proxy contract directly. If you changed the admin of a single proxy to an Address A, then A is managing that proxy directly and not via the ProxyAdmin. In that case, you need to call changeAdmin on the proxy from A.
    • Alternatively, you can send the below transactions directly from your hardware wallet with a web3 provider that connects to it, such as this one or this one. (Disclaimer: Be especially careful here, as those providers have not been tested/audited by OpenZeppelin).
  2. Use npx oz upgrade to upgrade a single contract, or npx oz upgrade --all to upgrade all contracts.

  3. If you’ve added any new initialization functions that need to be called after upgrading, call them now. Since you won’t be able to use the initializer modifier again, make sure these functions are protected from being called more than once. See this post for an example implementation.

  4. If necessary, repeat steps 4 and 5 in the above section (verify contracts and change upgradeability admin)

  5. If we ever decide we no longer want a contract to be upgradable, use npx oz set-admin to change the upgradeability admin to the zero address.

Other Tips and Notes

In the CLI commands docs, you’ll see a few other commands not used or mentioned above that will be discussed here.

The session command is pretty handy. It’s there to make it easy to run many operations over the same network. You can run oz session --network rinkeby , and all subsequent operations will be executed on Rinkeby automatically, so you don’t need to manually choose the network on every command. This command can also be used to set the default address transactions are sent from.

You don’t need to worry about freeze, publish, and bump unless you are creating an Ethereum package (ie a reusable contracts library) yourself.

The add and push commands are there mostly for historic reasons. In the first versions of the CLI, in order to create a contract, you had to first add it to your project, then push the implementation contract, and then create the upgradeable instance. In more recent versions, just running create takes care of everything for you (note that you still need to manually run the add/push/create flow if you are running in no-interactive mode, though this is probably going to change as well in the next major release).

5 Likes

This is great @msolomon4! Thanks so much for setting it up!

1 Like

No problem, and thanks again for your help!

1 Like

Thanks @msolomon4 for writing this,

I have turned the post into a Wiki so that it can be edited/added to by anyone in the community in future.

If we are only importing from OpenZeppelin Contracts (Ethereum Package) and aren’t using the Standalone contracts in Contracts Ethereum Package, we can just do npm i @openzeppelin/contracts-ethereum-package rather than npx oz link @openzeppelin/contracts-ethereum-package.

With regards testing, we can test the logic contracts with unit tests (https://docs.openzeppelin.com/learn/writing-automated-tests), and we can also test our contracts using their proxies.

1 Like