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

:computer: Environment

Any OpenZeppelin SDK version

:memo:Details

I’ve read through the documentation, but I’m still not totally clear on what a full start-to-finish workflow would look like with the OpenZeppelin CLI.

There’s a few commands where I’m not sure how they’d fit in. These are:

  • npx oz freeze
  • npx oz publish
  • npx oz push
  • npx oz session

Here is my current best shot at putting together this full workflow, which doesn’t use the commands above. Please me know which parts of the below workflow are incorrect or can be improved upon!

Setup, Development, and Testing

  1. Initialize our project with npx oz init projectName

  2. Link the OpenZeppelin contracts we’ll be using as dependencies with npx oz link @openzeppelin/contracts-ethereum-packageWhat exactly is happening here that’s different than manually installing an npm package?

Now we develop our contracts, test them, and just play around with the CLI to get a feel for things

Deployment

  1. When ready for “real” deployment to the mainnet or a testnet, we first reset the project using the steps in this forum post, summarized below:

    1. Delete the build directory

    2. Delete all <network>.json files

    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. Add all contracts you will be deploying to your project to the project.json file by running npx oz add contractName1 contractName2

  4. Deploy each contract with npx oz create. During these step we also call the initialize() function on each contract.

  5. Verify them on Etherscan/Etherchain with npx oz verify

  6. Now our initial deployments are done. To reduce the risk of malicious upgrades we want to change the upgradeability admin for all contracts to be a hardware wallet. We do this with npx oz set-admin

At this point we are done with deployment and are contract/dapp can be used by others on whichever network we deployed to.

Upgrade Contracts

  1. Our contracts have been running for some time, and we now want to upgrade one or more of them

  2. Upgrade the version number using npx oz bump <new-version>, and manually upgrade the version number in package.json.

  3. Now we change the admin back from the hardware wallet using npx oz set-adminBut how do we send this transaction from the hardware wallet. Is a certain npm package needed to interface with it?

  4. Use npx oz upgrade to upgrade a single contract, or npx oz upgrade --all to upgrade all contracts.

  5. Repeat steps 5-6 of the previous section (verify, change upgradeability admin)

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

2 Likes

Hey @msolomon4! This is a great overview, thanks for putting it together. Let me answer your questions inline.

You do not need to worry about freeze, publish, or bump unless you are creating an Ethereum package (ie a reusable contracts library) yourself.

add and push 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).

As for session, 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.

It will also load 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.

Actually, the CLI does a pretty good job at keeping stuff from different networks compartmentalized. You should only need to nuke your current settings if you want to deploy from scratch on the same network, but if you are moving from development to testnet, or testnet to mainnet, you don't need to worry about this.

This step is automatically handled by create in recent versions, so you don't need to do it yourself.

This step is not actually required if you are not publishing an Ethereum package yourself, and can safely omit it.

If the owner is a hardware wallet, you should be able to use a web3 provider that connects to the hardware wallet, such as this one (disclaimer: I have not tested nor audited that one, you need to be especially careful here!). Alternatively, you can manually craft the transaction to transferOwnership (I can help you here if you need, since we don't have an automated tool to handle this yet).


Now, I think the steps you listed are super valuable. If you want to clean them up a bit, I think they would be great material for the Guides section of the forum!

2 Likes

Hey @spalladino, thanks so much—pretty much cleared everything up in one fell swoop!

Two quick things I’d like to confirm regarding the transaction to change the upgradeability admin away from the hardware wallet:

  1. In the case where all contracts are controlled by the same admin address, the transferOwnership transaction is made on the contract address given by the contracts.proxyAdmin.address field of <network>.json?

  2. In the case where contracts have different upgradeability admin addresses, the transferOwnership transaction is made on the contract address given by the contracts.proxies.<contract-name>.admin field of <network>.json?


I’ll definitely clean it up and put together a guide. Once that’s done, should I post it as a comment here for review before moving it to the guides section, or is it preferable to just post it there and edit the post with any fixes/feedback?

2 Likes

Exactly! That admin address corresponds to the ProxyAdmin contract, which acts as admin of all your proxies (and your EOA is in turn the owner of the ProxyAdmin). When sending transferOwnership to the ProxyAdmin instance, you are changing the owner of the ProxyAdmin itself.

In this case you send the tx 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.

Whatever works best for you! Thanks a lot for contributing!!

1 Like

The guide @msolomon4 put together:

Hi guys, I changed the ownership of a specific contract (proxy) to a local account address. Now I want to change it back to the proxyAdmin to make an upgrade, however doing this through web3 requires a json of the proxy contract, what json should I use?
Thanks in advance.

1 Like

You can fetch the abi of the AdminUpgradeabilityProxy in the upgrades package, at node_modules/@openzeppelin/upgrades/build/contracts/AdminUpgradeabilityProxy.json. I’m including it here for convenience as well:

[
  {
    "constant": false,
    "inputs": [
      {
        "name": "newImplementation",
        "type": "address"
      }
    ],
    "name": "upgradeTo",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "newImplementation",
        "type": "address"
      },
      {
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "upgradeToAndCall",
    "outputs": [],
    "payable": true,
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [],
    "name": "implementation",
    "outputs": [
      {
        "name": "",
        "type": "address"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "newAdmin",
        "type": "address"
      }
    ],
    "name": "changeAdmin",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "constant": false,
    "inputs": [],
    "name": "admin",
    "outputs": [
      {
        "name": "",
        "type": "address"
      }
    ],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "name": "_logic",
        "type": "address"
      },
      {
        "name": "_admin",
        "type": "address"
      },
      {
        "name": "_data",
        "type": "bytes"
      }
    ],
    "payable": true,
    "stateMutability": "payable",
    "type": "constructor"
  },
  {
    "payable": true,
    "stateMutability": "payable",
    "type": "fallback"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "name": "previousAdmin",
        "type": "address"
      },
      {
        "indexed": false,
        "name": "newAdmin",
        "type": "address"
      }
    ],
    "name": "AdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "name": "implementation",
        "type": "address"
      }
    ],
    "name": "Upgraded",
    "type": "event"
  }
]

Of course, please test the upgrade before running it. You can use ganache forks for this, see this guide for more info.

2 Likes

A post was split to a new topic: Update the administrator with the command set-admin