Git Product home page Git Product logo

openzeppelin-upgrades's Introduction

OpenZeppelin Upgrades

Docs Coverage Status

Integrate upgrades into your existing workflow. Plugins for Hardhat and Foundry to deploy and manage upgradeable contracts on Ethereum.

  • Deploy upgradeable contracts.
  • Upgrade deployed contracts.
  • Manage proxy admin rights.
  • Easily use in tests.

Installation and Usage

See the documentation for each plugin:

Hardhat Foundry

How do the plugins work?

The plugins provide functions which take care of managing upgradeable deployments of your contracts.

For example, deployProxy does the following:

  1. Validates that the implementation is upgrade safe.

  2. Deploys the implementation contract. Note that the Hardhat plugin first checks if there is an implementation contract deployed with the same bytecode, and skips this step if one is already deployed.

  3. Creates and initializes the proxy contract, along with a proxy admin (if needed).

And when you call upgradeProxy:

  1. Validates that the new implementation is upgrade safe and is compatible with the previous one.

  2. Deploys the new implementation contract. Note that the Hardhat plugin first checks if there is an implementation contract deployed with the same bytecode, and skips this step if one is already deployed.

  3. Upgrades the proxy to use the new implementation contract.

The Hardhat plugin keeps track of all the implementation contracts you have deployed in an .openzeppelin folder in the project root. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as .openzeppelin/unknown-*.json).

The Foundry plugin does not keep track of implementation contracts, but requires you to define reference contracts in order to validate new versions of implementations for upgrade safety.

Proxy patterns

The plugins support the UUPS, transparent, and beacon proxy patterns. UUPS and transparent proxies are upgraded individually, whereas any number of beacon proxies can be upgraded atomically at the same time by upgrading the beacon that they point to. For more details on the different proxy patterns available, see the documentation for Proxies.

For UUPS and transparent proxies, use deployProxy and upgradeProxy. For beacon proxies, use deployBeacon, deployBeaconProxy, and upgradeBeacon. See the documentation for Hardhat Upgrades and Foundry Upgrades for examples.

Managing ownership

Transparent proxies have an admin address which has the rights to upgrade them. By default, the admin is a proxy admin contract deployed behind the scenes. Keep in mind that the admin of a proxy can only upgrade it, but not interact with the implementation contract. Read here for more info on this restriction.

The proxy admin contract also defines an owner address which has the rights to operate it. By default, the proxy admin's owner is the initialOwner address used during deployment of the transparent proxy if provided, otherwise it is the externally owned account used during deployment. You can change the proxy admin owner by calling the admin.transferProxyAdminOwnership function in the Hardhat plugin, or the transferOwnership function of the proxy admin contract if using Foundry.

Warning

Do not reuse an already deployed ProxyAdmin. Before @openzeppelin/contracts version 5.x, the address provided to transparent proxies was an initialAdmin as opposed to an initialOwner of a newly deployed ProxyAdmin. Reusing a ProxyAdmin will disable upgradeability in your contract.

UUPS and beacon proxies do not use admin addresses. UUPS proxies rely on an _authorizeUpgrade function to be overridden to include access restriction to the upgrade mechanism, whereas beacon proxies are upgradable only by the owner of their corresponding beacon.

Once you have transferred the rights to upgrade a proxy or beacon to another address, you can still use your local setup to validate and deploy the implementation contract. The plugins include a prepareUpgrade function that will validate that the new implementation is upgrade-safe and compatible with the previous one, and deploy it using your local Ethereum account. You can then execute the upgrade itself from the admin or owner address. You can also use the defender.proposeUpgrade or defender.proposeUpgradeWithApproval functions to automatically set up the upgrade in OpenZeppelin Defender.

Community

Join the OpenZeppelin forum to ask questions or discuss about these plugins, smart contracts upgrades, or anything related to Ethereum development!

License

OpenZeppelin Upgrade plugins are released under the MIT License.

openzeppelin-upgrades's People

Contributors

0xmichalis avatar abcoathup avatar amxx avatar arijoon avatar cds-amal avatar cruzdanilo avatar dependabot[bot] avatar dylankilkenny avatar ericglau avatar fanqiaojun avatar frangio avatar githubpang avatar islishude avatar jamie-arpeggi avatar jawadoc avatar jihoonsong avatar julianmrodri avatar julissadantes avatar kevinji avatar kumacrypto avatar llwslc avatar martriay avatar pcaversaccio avatar renovate[bot] avatar shivamarora avatar spalladino avatar sujantkumarkv avatar thegostep avatar timelytoga avatar tirumerla avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openzeppelin-upgrades's Issues

Review potential race conditions in deployProxy

Depending on whether deployment promises resolve once the transaction was broadcast or mined, it may happen that two consecutive proxy deploys fail at the second one:

const proxy1 = await deployProxy(Impl, []);
const proxy2 = await deployProxy(Impl, []);

The first one will deploy Impl and put it in the impl store, the second one will fetch its address and verify that it has code using eth_getCode, but if it hasn't been mined yet it will fail (unless there is a way to call eth_getCode on the mempool?).

Note that this will probably not be detected by testing on the Buidler EVM (#21).

Deploy implementations and ProxyAdmin concurrently

We currently serialize the deployment transactions for an implementation and a ProxyAdmin contract. For a faster experience we may want to send them to the blockchain at the same time. On the other hand, deploying a ProxyAdmin is only meant to happen once, and adding concurrency may increase the chance of odd issues happening, so we can decide to keep them serialized.

See 0004212 for an implementation of this idea for Buidler.

Use contracts build directory from Truffle config

There was a global config object introduced in Truffle 5.1.35. It includes the contracts_build_directory, which is sometimes an automatic temporary directory, and is where we should be reading artifacts from instead of the currently hardcoded build/contracts directory.

Validate external linked libraries

At the moment of deployProxy, we have to validate that the linked libraries are using code that has been validated to be upgrade safe. In the solc output for a contract we find the locations in the bytecode where the addresses of linked libraries will be found. During deployProxy we have to extract those addresses, fetch the bytecode at that address, and look it up in the validations object.

We may have issues with fetching the bytecode if the library deployment hasn't been mined yet. I think this is a real concern that we should think about a little bit before proceeding. This is what I meant in the first paragraph. It's different from #22 because we deploy those impls ourselves, but in my proposed workflow for libraries, it's the user who deploys those libraries so we don't have the information about the library deployment (e.g. "version", txid, etc.) unless we can somehow retrieve the bytecode from the address.

Review storage validations for structs and enums

I don't think structs are enums are supported in storage layout checking. We don't have any specific code to handle that, and I think we may have to specifically check that there are no structs and enums and let the user know that we can't automatically verify their safety. We may also want to add a way to force an upgrade even if this can't be checked.

This will be until #3 is implemented.

Remove AST ids from storage layout

The extracted storage layout contains type identifiers that contain AST node ids, such as t_struct(MyComplexStruct)1029_storage where 1029 is a node id.

I wrote a function stabilizeTypeIdentifier that removes node ids, but it would be an error to use it as is.

// Type Identifiers contain AST id numbers, which makes them sensitive to
// unrelated changes in the source code. This function stabilizes a type
// identifier by removing all AST ids.
function stabilizeTypeIdentifier(typeIdentifier: string): string {

The problem is this transformation on type identifiers can produce clashes if there is a type (e.g. a struct) with the same name defined in different places.

struct S {
    uint u;
}

contract CParent {
    S s1;
}

contract CChild is CParent {
    struct S {
        uint u1;
        uint u2;
    }
    S s2;
}

The storage layout we get for contract CChild, once we include also the inherited state variables, will contain instances of both the top level S struct and the CChild.S struct, which have different layouts. Simply removing node ids from type identifiers will result in two equal type identifiers for different types.

We will run into this same issue once we use the storage layout returned by solc itself (#3).

I haven't thought about potential solutions yet. Maybe we can use the fully qualified name of the struct (e.g. CChild.S) in the type identifier.

Swapping the order in which the base contracts are declared doesn't result in storage layout error

Swapping the order in which the base contracts are declared doesn't result in storage layout error

Example below MyContract is A, B whilst MyContractV2 is B, A

//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract A {
    uint256 private a;
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract B {
    string private b;
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "./A.sol";
import "./B.sol";

contract MyContract is A, B {
    function initialize() public {}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "./A.sol";
import "./B.sol";

contract MyContractV2 is B, A {
}

Improve format of error messages

Reporting of failed validations and storage incompatibilities should be well polished.

Ideally we would print the line number where the error is presented, but initially we can print the name of the variable that introduced it.

Support specifying an upgrade function and parameters in upgradeProxy

Need a mechanism to be able to initialize state when upgrading so that it is part of the upgrade transaction.
Otherwise users would need to protect with some form of access control so they could do this in a separate transaction.

For example, adding a new state variable otherValue in version 2 and needing a safe way to initialize it on upgrade.

    function upgradeV2(uint256 initialValue) public {
        require (!_upgradeV2, "Box: already upgraded to V2");
        _upgradeV2 = true;
        
        otherValue = initialValue;
    }

Validate linked libraries

We currently don't check that a contract's linked libraries are also upgrade safe. The check for delegatecall leaves out libraries by looking for baredelegatecall types.

if (fn.typeDescriptions.typeIdentifier?.match(/^t_function_baredelegatecall_/)) {
yield { kind: 'delegatecall' };
}

The difficulty with libraries is that it's not known at compile time what implementation is going to be linked. One option we could follow is to store the output from the compiler that specifies the bytecode locations where library addresses are inserted, then extract those addresses from a linked contract, obtain their version id (by fetching the bytecode and hashing it), and then look for the corresponding information in the manifest or in the validations cache (in the latter case then storing it in the manifest). We can assume for now that a contract will always be linked to a library whose source code will be present in the current project.

Consider using keccak256 for bytecode hashing

I initiall used sha256 because it's available without dependencies, but this will make it impossible to migrate OpenZeppelin CLI manifests as in #15. It's also more common in Ethereum to use keccak256 even for hashes that will only live off chain.

Truffle: Provide access to proxy address for upgrades

Currently there doesn't appear to be a mechanism to get the proxy address other than copy the address from the output of deploying AdminUpgradeabilityProxy and hardcode it into migration scripts.

3_upgrade.js

const GreeterV2 = artifacts.require('GreeterV2');

const { upgradeProxy } = require('@openzeppelin/upgrades-truffle');

module.exports = async function (deployer) {
  const proxyAddress = "0x72440269630E393d38975Db7fA7Cb4D14e7eC061";
  await upgradeProxy(proxyAddress, GreeterV2, { deployer });
};
$ npx truffle console --network development
truffle(development)> const proxyAddress = "0x72440269630E393d38975Db7fA7Cb4D14e7eC061";
undefined
truffle(development)> greeter = await Greeter.at(proxyAddress)
undefined

Document suggestions for manifests under version control

Using the plugins to deploy proxies creates manifest files under the .openzeppelin directory. They are currently named 0x1234.json but in #19 they will be renamed to friendlier names like mainnet.json.

We need to have a clear recommendation on whether to put these files under version control.

The recommendation that I think makes sense generally is that all manifests except those for local development networks (Ganache, Buidler EVM) should be put under version control. I think this is also the current recommendation put forward for OpenZeppelin CLI users.

The version control recommendation is relevant to the naming decision in #19. It would be easier if we could propose a .gitignore rule like .openzeppelin/dev-*. If we decide to go with names like ganache.json and buidlerevm.json, each of those would need to have their own entry in .gitignore.

What is the best place to tell users to add the relevant .gitignore rules? I think it would make sense to make it a part of the installation instructions.

Reuse Truffle and Ethers tx waiting implementations or parameters

In #47 I introduced waitAndValidateDeployment to wait until a tx is mined.

export async function waitAndValidateDeployment(provider: EthereumProvider, deployment: Deployment): Promise<void> {

There's at least two parameters here that te user might want to configure: the polling interval and the timeout. Libraries like Web3, Truffle, and Ethers already allow the user to configure these parameters and we should try to reuse them. We can also evaluate reusing their implementations for waiting a tx instead of rolling our own.

Add locking mechanism to Manifest

To avoid race conditions and corrupting the manifest file if it's ever used by two processes concurrently.

The OpenZeppelin CLI does a very coarse locking which only allows running a single instance at the same time. We should aim for finer locking, which we should be able to do given the way Manifest was designed.

Support initializer corner cases

  1. Contracts with no initialize function.
  2. Contracts with two initialize functions with different signatures. What should happen in this case?

Additionally, we might want to make the array argument optional for initializers with no arguments.

Use storage slots for implementation and admin

We currently retrieve the implementation using a function call:

const currentImplAddress = await proxy.callStatic.implementation({ from: signer });

We should read directly from storage using getStorageAt.

I would introduce a new module to contain this new getImplementation function. There should also be a getAdmin function even though it won't be used until #2 is implemented.

Improve upgrade error messages

Can we improve upgrade error messages to not include the stack trace?

Error: New storage layout is incompatible
    at Object.assertStorageUpgradeSafe (/home/abcoathup/projects/uptruf/node_modules/@openzeppelin/upgrades-truffle/node_modules/@openzeppelin/upgrades-core/src/storage.ts:56:11)
    at upgradeProxy (/home/abcoathup/projects/uptruf/node_modules/@openzeppelin/upgrades-truffle/src/index.ts:94:3)
Truffle v5.1.36 (core: 5.1.36)
Node v10.21.0

Offer a way to safely rename state variables across upgrades

State variable renaming is not allowed for upgrades, because it could have actually been an error by the user which implies a storage layout change.

We should offer a way to rename anyway. My preference would be a way for developers to explicitly mark a state variable as being a rename of the previously existing variable in the same storage location. Something like...

string private admin; // formerly: owner

Support using a non-default signer

The Buidler plugin will currently deploy both impl and proxy using the default signer. We should allow users to specify a different signer.

Instead of receing a contractName: string as argument to deployProxy and upgradeProxy, we could receive a ContractFactory instance which the user can create with any signer they want. Since the only element identifying the contract in the ContractFactory is the creation bytecode, this may imply changes in the format we identify contracts, since the version id is currently the hash of the deployed bytecode.

Use storage layout from solc output for more sophisticated storage checks

Solidity 0.5.13 introduced the storageLayout compiler output, which gives accurate information about the size and alignment of items in a storage layout. We should use this information instead of the AST. Given this accurate information, the use of the Levenshtein algorithm may have to change quite a bit.

Separate deployProxy and upgradeProxy tests

Currently the tests for Truffle and buidler have deployProxy and upgradeProxy in the same script. In production they will be used in separate scripts as an upgrade will happen sometime after a deploy.

The existing upgrades library shows a single script doing deploy and upgrade and this has caused confusion as there aren't examples showing these distinct steps.

The tests act as examples (and will likely be used as the basis for documentation examples), so we should show how they intend to be used.

For example in Truffle we could use something like the following:

2_deploy.js

const Greeter = artifacts.require('Greeter');
const GreeterV2 = artifacts.require('GreeterV2');

const { deployProxy } = require('@openzeppelin/upgrades-truffle');

module.exports = async function (deployer) {
  await deployProxy(Greeter, ['Hello Truffle'], { deployer });
};

3_upgrade.js

const Greeter = artifacts.require('Greeter');
const GreeterV2 = artifacts.require('GreeterV2');

const { upgradeProxy } = require('@openzeppelin/upgrades-truffle');

module.exports = async function (deployer) {
  const greeter = await Greeter.deployed();
  await upgradeProxy(greeter.address, GreeterV2, { deployer });
};

Implement migration from CLI manifests to the new format

I started building the manifest format from scratch because it was easier for me, but we have to decide whether we'll reuse the format that exists in OpenZeppelin CLI. It turns out that the existing CLI manifest contains a lot of information that is irrelevant for the plugins so we will define a new one. Thus we have to add the manifestVersion field (3.0?) and implementing automatic migration from older to newer manifest version for users who decide to migrate to the plugins.

Renaming state variable results in error New storage layout is incompatible

Renaming state variable results in error New storage layout is incompatible

Renaming doesn't change storage layout.

//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract MyContract {
    uint256 private x;
    string private y;

    function initialize() public {}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract MyContractV2 {
    uint256 private x;
    string private renamey;
}

Truffle: display notification on upgrade

When using upgradeProxy no notification is shown that the upgrade has occurred.

All we see is that a new logic contract has been deployed:

$ npx truffle migrate --network development
 ...

   Deploying 'GreeterV2'
   ---------------------
   > transaction hash:    0x5336034f54058e62cd24f3e2ef1d408203a4312e8255e2a4e9806292f37367db
   > Blocks: 0            Seconds: 0
   > contract address:    0x55Db01E043a48bc0d11037c51E3911A9Eb8dbe24
   > block number:        212
   > block timestamp:     1596005809
   > account:             0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
   > balance:             99.06622414
   > gas used:            290127 (0x46d4f)
   > gas price:           20 gwei
   > value sent:          0 ETH
   > total cost:          0.00580254 ETH


   > Saving migration to chain.
   > Saving artifacts
   -------------------------------------
   > Total cost:          0.04147856 ETH

Expose ProxyAdmin functions

A new module admin containing two new funcitons to handle admin and ownership.

  • admin.transferOwnership
  • admin.changeProxyAdmin

Validate proxy and proxyadmin owner before sending a transaction

If the current sender does not own the ProxyAdmin, they get a revert with an Ownable error:

Error: Returned error: VM Exception while processing transaction: revert Ownable: caller is not the owner -- Reason given: Ownable: caller is not the owner.

As a nice to have, we could prevent this by checking if the sender is the owner of the proxyadmin before sending the tx. And since we're at it, we can also check that the proxy to upgrade is indeed owned by the proxyadmin.

Use user-friendly names for manifest files

We're currently using ${chainId}.json, which results in files named like 0x7a69.json. OpenZeppelin CLI uses a user friendly network name like mainnet.json, and anything that doesn't have a known name gets called dev-${networkId}.json. I think user friendly names make sense so that people know what the file is, but I don't like the dev- naming because some unknown ids will not be development, so maybe it should be id-.

Remove metadata before hashing bytecode for versioning

The function getVersionId takes a hash of the deployedBytecode. This bytecode contains a hash of the metadata of the source files, which can change due to simple formatting changes in source files. We want to remove this metadata before taking the hash, so that changing formatting or comments doesn't result in a new "version" of the bytecode.

OpenZeppelin CLI should already have code that does this.

Look into potential issues with concurrent Buidler runs

See 8be3822. Had to disable concurrency in Buidler tests that were using different instances of the Buidler EVM at the same time. This may be related to the fact that Buidler runs are deterministic, but it's weird that even tx hashes would be the same. We need to understand exactly what was going on there.

Fix Truffle error messages

Truffle is ignoring our util.inspect.custom error display logic and as a result the user doesn't see validation errors. It seems it only picks up the message property in the error object.

We have to change our UpgradesError class so that it works in that context. The solution may be to add a message getter to our UpgradesError class that returns the full message + details.

We should still keep [util.inspect.custom] to get color in the terminal under Buidler.

Resumable transactions

If the plugins are interrupted while waiting for a transaction to be mined, we should make it possible to re-run it and pick up where it was interrupted. It should not be necessary to re-send the transaction (paying the cost again).

The way that we implement this is by storing on disk the transaction id as soon as it's sent to the network. The way I see this designed for deployments is that wherever we can store an object { address: string } (for example for storing the address of the proxy admin) we can also store an object { tx: string } that indicates a transaction is in progress and should be awaited instead of deploying. We have to consider the possibility that that tx never gets mined and how to detect that. For transactions which are not deployments, such as upgradeTo, I'm not sure where we should store the tx id.

Use ProxyAdmin

This can be developed in two stages:

  1. Deploy instances with a ProxyAdmin
  2. Reuse ProxyAdmin

Refactor frontend

Now that we have both Truffle and Buidler plugins, there is a lot of duplication between their respective deployProxy and upgradeProxy functions, there is even duplication between them within the same plugin, and they are also incidentally very long functions.

It seems to me that we should be able to write abstract deployProxy and upgradeProxy functions that could be instantiated with the Truffle- or Buidler-specific parts.

I think what has to be astracted is:

  • provider
  • where validations come from
  • how the impl is depoyed, given a contract factory
  • how the proxy is deployed
  • how the initialize data is encoded
  • how to obtain the instance that will be returned

Write basic API documentation

We may want to briefly experiment with using TypeDoc to generate input for the docs site. But if it's too complicated we should just write it fully by hand.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.