Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial docs #142

Merged
merged 17 commits into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 7 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,123 +75,40 @@ it('works before and after upgrading', async function () {
});
```

## Usage


## How do the plugins work?
abcoathup marked this conversation as resolved.
Show resolved Hide resolved

Both plugins provide two main functions, `deployProxy` and `upgradeProxy`, which take care of managing upgradeable deployments of your contracts. In the case of `deployProxy`, this means:

1. Validate that the implementation is [upgrade safe](#what-does-it-mean-for-a-contract-to-be-upgrade-safe)
1. Validate that the implementation is [upgrade safe](https://docs.openzeppelin.com/upgrades-plugins/faq#what-does-it-mean-for-a-contract-to-be-upgrade-safe)

2. Deploy a [proxy admin](#what-is-a-proxy-admin) for your project
2. Deploy a [proxy admin](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-a-proxy-admin) for your project

3. Deploy the [implementation contract](#what-is-an-implementation-contract)
3. Deploy the [implementation contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-an-implementation-contract)

4. Create and initialize the proxy contract

And when you call `upgradeProxy`:

1. Validate that the new implementation is [upgrade safe](#what-does-it-mean-for-a-contract-to-be-upgrade-safe) and is [compatible](#what-does-it-mean-for-an-implementation-to-be-compatible) with the previous one
1. Validate that the new implementation is [upgrade safe](https://docs.openzeppelin.com/upgrades-plugins/faq#what-does-it-mean-for-a-contract-to-be-upgrade-safe) and is [compatible](https://docs.openzeppelin.com/upgrades-plugins/faq#what-does-it-mean-for-an-implementation-to-be-compatible) with the previous one

2. Check if there is an [implementation contract](#what-is-an-implementation-contract) deployed with the same bytecode, and deploy one if not
2. Check if there is an [implementation contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-an-implementation-contract) deployed with the same bytecode, and deploy one if not

3. Upgrade the proxy to use the new implementation contract

The plugins will keep track of all the implementation contracts you have deployed in an `.openzeppelin` folder in the project root, as well as the proxy admin. 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`).

> Note: the format of the files within the `.openzeppelin` folder is not compatible with those of the [OpenZeppelin CLI](https://docs.openzeppelin.com/cli/2.8/). If you want to use these plugins for an existing OpenZeppelin CLI project, we will be sharing soon a guide on how to migrate.
> Note: the format of the files within the `.openzeppelin` folder is not compatible with those of the [OpenZeppelin CLI](https://docs.openzeppelin.com/cli). If you want to use these plugins for an existing OpenZeppelin CLI project, we will be sharing soon a guide on how to migrate.
## Managing ownership

All proxies define an _admin_ address which has the rights to upgrade them. By default, the admin is a [proxy admin contract](#what-is-a-proxy-admin) deployed behind the scenes. You can change the admin of a proxy by calling the `admin.changeAdminForProxy` function in the plugin. Keep in mind that the _admin_ of a proxy can only upgrade it, but not interact with the implementation contract. Read [here](https://docs.openzeppelin.com/upgrades/2.8/proxies#transparent-proxies-and-function-clashes) for more info on this restriction.
All proxies define an _admin_ address which has the rights to upgrade them. By default, the admin is a [proxy admin contract](https://docs.openzeppelin.com/upgrades-plugins/faq#what-is-a-proxy-admin) deployed behind the scenes. You can change the admin of a proxy by calling the `admin.changeAdminForProxy` function in the plugin. Keep in mind that the _admin_ of a proxy can only upgrade it, but not interact with the implementation contract. Read [here](https://docs.openzeppelin.com/upgrades-plugins/proxies#transparent-proxies-and-function-clashes) for more info on this restriction.

The proxy admin contract also defines an _owner_ address which has the rights to operate it. By default, this address is the externally owned account used during deployment. You can change the proxy admin owner by calling the `admin.transferProxyAdminOwnership` function in the plugin. Note that changing the proxy admin owner effectively transfers the power to upgrade any proxy in your whole project to the new owner, so use with care.

Once you have transferred the rights to upgrade a proxy 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 address.

Refer to each plugin documentation for more details on the `admin` functions.

## FAQ

### What does it mean for a contract to be upgrade safe?

When deploying a proxy for a contract, there are some limitations to the contract code. In particular, the contract cannot have a constructor, and should not use the `selfdestruct` or `delegatecall` operations for security reasons.

As a replacement for the constructor, it is common to set up an `initialize` function to take care of the contract's initialization. You can use the [`Initializable`](https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable#initializers) base contract to have access to an `initializer` modifier that ensures the function is only called once.

```solidity
import "@openzeppelin/upgrades-core/contracts/Initializable.sol";
// Alternatively, if you are using @openzeppelin/contracts-ethereum-package:
// import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";
contract MyContract is Initializable {
uint256 value;
function initialize(uint256 initialValue) public initializer {
value = initialValue;
}
}
```

Both plugins will validate that the contract you are trying to deploy complies with these rules. You can read more about how to write upgrade safe contracts [here](https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable).

### What does it mean for an implementation to be compatible?

When upgrading a proxy from one implementation to another, the _storage layout_ of both implementations must be compatible. This means that, even though you can completely change the code of the implementation, you cannot modify the existing contract state variables. The only operation allowed is to append new state variables after the ones already declared.

Both plugins will validate that the new implementation contract is compatible with the previous one. However, the plugins currently do not support validating custom types (enums or structs). To force a deployment where custom types are involved, set the `unsafeAllowCustomTypes` flag to true in the `deployProxy` or `upgradeProxy` call.

You can read more about how to make storage-compatible changes to an implementation contract [here](https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable#modifying-your-contracts).

### What is a proxy admin?

A [`ProxyAdmin`](packages/core/contracts/proxy/ProxyAdmin.sol) is a contract that acts as the owner of all your proxies. Only one per network gets deployed. When you start your project, the `ProxyAdmin` is owned by the deployer address, but you can transfer ownership of it by calling [`transferOwnership`](https://docs.openzeppelin.com/contracts/3.x/api/access#Ownable-transferOwnership-address-).

You can read more about this contract [here](https://docs.openzeppelin.com/cli/2.8/contracts-architecture#proxyadmin.sol).

### What is an implementation contract?

Upgradeable deployments require at least two contracts: a proxy and an implementation. The proxy contract is the instance you and your users will interact with, and the implementation is the contract that holds the code. If you call `deployProxy` several times for the same implementation contract, several proxies will be deployed, but only one implementation contract will be used.

When you upgrade a proxy to a new version, a new implementation contract is deployed if needed, and the proxy is set to use the new implementation contract. You can read more about the proxy upgrade pattern [here](https://docs.openzeppelin.com/upgrades/2.8/proxies).

### What is a proxy?

A proxy is a contract that delegates all of its calls to a second contract, named an implementation contract. All state and funds are held in the proxy, but the code actually executed is that of the implementation. A proxy can be _upgraded_ by its admin to use a different implementation contract.

You can read more about the proxy upgrade pattern [here](https://docs.openzeppelin.com/upgrades/2.8/proxies).

### Why can't I use `immutable` variables?

Solidity 0.6.5 [introduced the `immutable` keyword]([https://github.com/ethereum/solidity/releases/tag/v0.6.5](https://github.com/ethereum/solidity/releases/tag/v0.6.5)) to declare a variable that can be assigned only once during construction and can be read only after construction. It does so by calculating its value during contract creation and storing its value directly into the bytecode.

Notice that this behavior is incompatible with the way upgradeable contracts work for two reasons:

1. Upgradeable contracts have no constructors but initializers, therefore they can't handle immutable variables.
2. Since the immutable variable value is stored in the bytecode its value would be shared among all proxies pointing to a given contract instead of each proxy's storage.

### Why can't I use external libraries?

At the moment the plugins do not support upgradeable contracts linked to external libraries. This is because it's not known at compile time what implementation is going to be linked thus making very difficult to guarantee the safety of the upgrade operation.

There are plans to add this functionality in the near future with certain constraints that make the issue easier to address like assuming that the external library's source code is either present in the codebase or that it's been deployed and mined so it can be fetched from the blockchain for analysis.

You can follow or contribute to [this issue in Github](https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/52).

### Why can't I use custom types like structs and enums?

At the moment the plugins do not support upgradeable contracts that implement or make use of custom types like structs or enums in their code or linked libraries. This is because of the additional complexity of checking for storage layout incompatibilities during an upgrade. (See ["What does it mean for an implementation to be compatible?"](#what-does-it-mean-for-an-implementation-to-be-compatible).)

In the mean time, we encourage users to either avoid using these kind of types or to manually check for [storage incompatibilities](https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable#modifying-your-contracts) and make use of the `unsafeAllowCustomTypes` flag available for the `deployProxy`, `upgradeProxy` and `prepareUpgrade` functions. If you're unsure about how to do this manual check, we'll be happy to help out with your situation if you [post in the forum](https://forum.openzeppelin.com).

You can follow or contribute to [this issue in Github](https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/95).

### Why do I have to recompile all contracts for Truffle?

Truffle artifacts (the JSON files in `build/contracts`) contain the AST (abstract syntax tree) for each of your contracts. Our plugin uses this information to validate that your contracts are [upgrade safe](#what-does-it-mean-for-a-contract-to-be-upgrade-safe).

Truffle sometimes partially recompiles only the contracts that have changed. We will ask you to trigger a full recompilation either using `truffle compile --all` or deleting the `build/contracts` directory when this happens. The technical reason is that since Solidity does not produce deterministic ASTs, the plugins are unable to resolve references correctly if they are not from the same compiler run.

## Community

Join the [OpenZeppelin forum](https://forum.openzeppelin.com/) to ask questions or discuss about these plugins, smart contracts upgrades, or anything related to Ethereum development!
Expand Down
10 changes: 10 additions & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
* xref:index.adoc[Overview]
* xref:truffle-upgrades.adoc[Using with Truffle]
* xref:buidler-upgrades.adoc[Using with Buidler]
* xref:writing-upgradeable.adoc[Writing Upgradeable Contracts]
* xref:proxies.adoc[Proxies]
* xref:network-files.adoc[Network Files]
* xref:faq.adoc[Frequently Asked Questions]
.API Reference
* xref:api-truffle-upgrades.adoc[Truffle Upgrades]
* xref:api-buidler-upgrades.adoc[Buidler Upgrades]
83 changes: 83 additions & 0 deletions docs/modules/ROOT/pages/api-buidler-upgrades.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
= OpenZeppelin Buidler Upgrades API

Both `deployProxy` and `upgradeProxy` functions will return instances of https://docs.ethers.io/v5/api/contract/contract[ethers.js contracts], and require https://docs.ethers.io/v5/api/contract/contract-factory[ethers.js contract factories] as arguments. All functions validate that the implementation contract is upgrade-safe, and will fail otherwise.

[[deploy-proxy]]
== deployProxy

Creates a proxy given an ethers contract factory to use as implementation, and returns a contract instance with the proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment.

Options are:

* `initializer`: sets a different initializer function to call
* `unsafeAllowCustomTypes`: allows a deployment where structs or enums are used in the implementation contract (required since xref:faq.adoc#what-does-it-mean-for-an-implementation-to-be-compatible[storage compatibility validations] do not handle custom types, so make sure the change you are introducing is safe)

[source,ts]
----
async function deployProxy(
Contract: ethers.ContractFactory,
args: unknown[] = [],
opts: { initializer: string, unsafeAllowCustomTypes: boolean } = {},
): Promise<ethers.Contract>
----

[[upgrade-proxy]]
== upgradeProxy

Upgrades a proxy at a specified address to a new implementation contract, and returns a contract instance with the proxy address and the new implementation interface.

Options are:

* `unsafeAllowCustomTypes`: allows an upgrade where structs or enums are used in the implementation contract (required since xref:faq.adoc#what-does-it-mean-for-an-implementation-to-be-compatible[storage compatibility validations] do not handle custom types, so make sure the change you are introducing is safe)

[source,ts]
----
async function upgradeProxy(
proxyAddress: string,
Contract: ethers.ContractFactory,
opts: { unsafeAllowCustomTypes: boolean } = {},
): Promise<ethers.Contract>
----

[[prepare-upgrade]]
== prepareUpgrade

Validates and deploys a new implementation contract, and returns its address. Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from Buidler.

Options are:

* `unsafeAllowCustomTypes`: allows an upgrade where structs or enums are used in the implementation contract (required since xref:faq.adoc#what-does-it-mean-for-an-implementation-to-be-compatible[storage compatibility validations] do not handle custom types, so make sure the change you are introducing is safe)

[source,ts]
----
async function prepareUpgrade(
proxyAddress: string,
Contract: ethers.ContractFactory,
opts: { unsafeAllowCustomTypes: boolean } = {},
): Promise<string>
----

[[admin-change-admin-for-proxy]]
== admin.changeAdminForProxy

Changes the admin for a specific proxy. Receives the address of the proxy to change, and the new admin address.

[source,ts]
----
async function changeAdminForProxy(
proxyAddress: string,
newAdmin: string,
): Promise<void>
----

[[admin-transfer-proxy-admin-ownership]]
== admin.transferProxyAdminOwnership

Changes the owner of the proxy admin contract, which is the default admin for upgrade rights over all proxies. Receives the new admin address.

[source,ts]
----
async function transferProxyAdminOwnership(
newAdmin: string,
): Promise<void>
----
Loading