Skip to content

Commit

Permalink
Add UnsafeUpgrades library variations, add LegacyUpgrades.sol (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau authored May 14, 2024
1 parent 7552dab commit 372170b
Show file tree
Hide file tree
Showing 52 changed files with 3,351 additions and 1,132 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.3.0 (2024-05-14)

- Adds library variations to support `forge coverage` or upgrade existing deployments using OpenZeppelin Contracts v4. ([#50](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/50))

### Breaking changes
- Removed the `CHEATCODE_ADDRESS` internal constant from `Upgrades.sol`.

## 0.2.3 (2024-05-02)

- Defender: Add `txOverrides` option. ([#49](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/49))
Expand Down
73 changes: 59 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

[![Docs](https://img.shields.io/badge/docs-%F0%9F%93%84-blue)](https://docs.openzeppelin.com/upgrades-plugins/foundry-upgrades)

Foundry library for deploying and managing upgradeable contracts, which includes upgrade safety checks.
Foundry library for deploying and managing upgradeable contracts, which includes upgrade safety validations.

## Installing

Follow one of the sections below depending on which version of OpenZeppelin Contracts you are using. OpenZeppelin Contracts v5 is required for new deployments.

### Using OpenZeppelin Contracts v5

Run these commands:
```console
forge install foundry-rs/forge-std
Expand All @@ -22,29 +26,38 @@ Set the following in `remappings.txt`, replacing any previous definitions of the
> **Note**
> The above remappings mean that both `@openzeppelin/contracts/` (including proxy contracts deployed by this library) and `@openzeppelin/contracts-upgradeable/` come from your installation of the `openzeppelin-contracts-upgradeable` submodule and its subdirectories, which includes its own transitive copy of `openzeppelin-contracts` of the same release version number. This format is needed for Etherscan verification to work. Particularly, any copies of `openzeppelin-contracts` that you install separately are NOT used.
### Windows installations
### Using OpenZeppelin Contracts v4

If you are using Windows, set the `OPENZEPPELIN_BASH_PATH` environment variable to the fully qualified path of the `bash` executable.
For example, if you are using [Git for Windows](https://gitforwindows.org/), add the following line in the `.env` file of your project (using forward slashes):
```env
OPENZEPPELIN_BASH_PATH="C:/Program Files/Git/bin/bash"
Run these commands, replacing `v4.9.6` with the specific version of OpenZeppelin Contracts that you are using:
```console
forge install foundry-rs/forge-std
forge install OpenZeppelin/openzeppelin-foundry-upgrades
forge install OpenZeppelin/[email protected]
forge install OpenZeppelin/[email protected]
```

Set the following in `remappings.txt`:
```
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
```

> **Note**
> Use [LegacyUpgrades.sol](src/LegacyUpgrades.sol) instead of `Upgrades.sol` to upgrade existing deployments that were created with OpenZeppelin Contracts v4.
## OpenZeppelin Defender integration

See [DEFENDER.md](DEFENDER.md)

## Version Limitations
## Foundry Requirements

This library requires [forge-std](https://github.com/foundry-rs/forge-std) version 1.8.0 or higher.

This library only supports proxy contracts and upgrade interfaces from OpenZeppelin Contracts versions 5.0 or higher.

## Before Running

This library uses the [OpenZeppelin Upgrades CLI](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core) for upgrade safety checks, which are run by default during deployments and upgrades.
This library uses the [OpenZeppelin Upgrades CLI](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core) for upgrade safety validations, which are run by default during deployments and upgrades.

If you want to be able to run upgrade safety checks, the following are needed:
If you want to be able to run upgrade safety validations, the following are needed:
1. Install [Node.js](https://nodejs.org/).
2. Configure your `foundry.toml` to enable ffi, ast, build info and storage layout:
```toml
Expand All @@ -57,7 +70,7 @@ extra_output = ["storageLayout"]
3. If you are upgrading your contract from a previous version, add the `@custom:oz-upgrades-from <reference>` annotation to the new version of your contract according to [Define Reference Contracts](https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core#define-reference-contracts) or specify the `referenceContract` option when calling the library's functions.
4. Run `forge clean` before running your Foundry script or tests, or include the `--force` option when running `forge script` or `forge test`.

If you do not want to run upgrade safety checks, you can skip the above steps and use the `unsafeSkipAllChecks` option when calling the library's functions. Note that this is a dangerous option meant to be used as a last resort.
If you do not want to run upgrade safety validations, you can skip the above steps and use the [`unsafeSkipAllChecks` option](src/Options.sol) when calling the `Upgrades` library's functions, or use the `UnsafeUpgrades` library instead. Note that these are dangerous options meant to be used as a last resort.

### Optional: Custom output directory

Expand All @@ -74,9 +87,25 @@ Then in a `.env` at your project root, set the `FOUNDRY_OUT` environment variabl
FOUNDRY_OUT=my-output-dir
```

### Windows environments

If you are using Windows, set the `OPENZEPPELIN_BASH_PATH` environment variable to the fully qualified path of the `bash` executable.
For example, if you are using [Git for Windows](https://gitforwindows.org/), add the following line in the `.env` file of your project (using forward slashes):
```env
OPENZEPPELIN_BASH_PATH="C:/Program Files/Git/bin/bash"
```

## Usage

Import the library in your Foundry scripts or tests:
Depending on which major version of OpenZeppelin Contracts you are using, and whether you want to run upgrade safety validations and/or use OpenZeppelin Defender, use the table below to determine which library to import:

| | OpenZeppelin Contracts v5 | OpenZeppelin Contracts v4 |
| --- | --- | --- |
| **Runs validations, supports Defender** | `import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";` | `import {Upgrades} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";` |
| **No validations, does not support Defender** | `import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";` | `import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";` |


Import one of the above libraries in your Foundry scripts or tests, for example:
```solidity
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
```
Expand All @@ -86,10 +115,12 @@ Also import the implementation contract that you want to validate, deploy, or up
import {MyToken} from "src/MyToken.sol";
```

Then call functions from [Upgrades.sol](src/Upgrades.sol) to run validations, deployments, or upgrades.
Then call functions from the imported library to run validations, deployments, or upgrades.

### Examples

The following examples assume you are using OpenZeppelin Contracts v5 and want to run upgrade safety validations.

Deploy a UUPS proxy:
```solidity
address proxy = Upgrades.deployUUPSProxy(
Expand Down Expand Up @@ -158,6 +189,20 @@ Upgrade a beacon:
Upgrades.upgradeBeacon(beacon, "MyContractV2.sol");
```

### Coverage Testing

To enable code coverage reports with `forge coverage`, use the following deployment pattern in your tests: instantiate your implementation contracts directly and use the `UnsafeUpgrades` library. For example:
```solidity
address implementation = address(new MyContract());
address proxy = Upgrades.deployUUPSProxy(
implementation,
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
```

> **Warning**
`UnsafeUpgrades` is not recommended for use in Forge scripts. It does not validate whether your contracts are upgrade safe or whether new implementations are compatible with previous ones. Ensure you run validations before any actual deployments or upgrades, such as by using the `Upgrades` library in scripts.

### Deploying and Verifying

Run your script with `forge script` to broadcast and deploy. See Foundry's [Solidity Scripting](https://book.getfoundry.sh/tutorials/solidity-scripting) guide.
Expand Down
12 changes: 1 addition & 11 deletions docs/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,5 @@ module.exports = {
templates: 'docs/templates',
exclude: ['internal'],
pageExtension: '.adoc',
pages: (_, file, config) => {
// For each contract file, find the closest README.adoc and return its location as the output page path.
const sourcesDir = path.resolve(config.root, config.sourcesDir);
let dir = path.resolve(config.root, file.absolutePath);
while (dir.startsWith(sourcesDir)) {
dir = path.dirname(dir);
if (fs.existsSync(path.join(dir, 'README.adoc'))) {
return path.relative(sourcesDir, dir) + config.pageExtension;
}
}
},
pages: 'files',
};
171 changes: 171 additions & 0 deletions docs/modules/api/pages/Defender.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
:github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]
:xref-Defender-Defender-deployContract-string-: xref:Defender.adoc#Defender-Defender-deployContract-string-
:xref-Defender-Defender-deployContract-string-struct-DefenderOptions-: xref:Defender.adoc#Defender-Defender-deployContract-string-struct-DefenderOptions-
:xref-Defender-Defender-deployContract-string-bytes-: xref:Defender.adoc#Defender-Defender-deployContract-string-bytes-
:xref-Defender-Defender-deployContract-string-bytes-struct-DefenderOptions-: xref:Defender.adoc#Defender-Defender-deployContract-string-bytes-struct-DefenderOptions-
:xref-Defender-Defender-proposeUpgrade-address-string-struct-Options-: xref:Defender.adoc#Defender-Defender-proposeUpgrade-address-string-struct-Options-
:xref-Defender-Defender-getDeployApprovalProcess--: xref:Defender.adoc#Defender-Defender-getDeployApprovalProcess--
:xref-Defender-Defender-getUpgradeApprovalProcess--: xref:Defender.adoc#Defender-Defender-getUpgradeApprovalProcess--
:deployContract: pass:normal[xref:#Defender-Defender-deployContract-string-[`++deployContract++`]]
:deployContract: pass:normal[xref:#Defender-Defender-deployContract-string-struct-DefenderOptions-[`++deployContract++`]]
:deployContract: pass:normal[xref:#Defender-Defender-deployContract-string-bytes-[`++deployContract++`]]
:deployContract: pass:normal[xref:#Defender-Defender-deployContract-string-bytes-struct-DefenderOptions-[`++deployContract++`]]
:proposeUpgrade: pass:normal[xref:#Defender-Defender-proposeUpgrade-address-string-struct-Options-[`++proposeUpgrade++`]]
:getDeployApprovalProcess: pass:normal[xref:#Defender-Defender-getDeployApprovalProcess--[`++getDeployApprovalProcess++`]]
:getUpgradeApprovalProcess: pass:normal[xref:#Defender-Defender-getUpgradeApprovalProcess--[`++getUpgradeApprovalProcess++`]]

[.contract]
[[Defender-Defender]]
=== `++Defender++` link:https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/blob/main/src/Defender.sol[{github-icon},role=heading-link]

[.hljs-theme-light.nopadding]
```solidity
import { Defender } from "openzeppelin-foundry-upgrades/Defender.sol";
```

Library for interacting with OpenZeppelin Defender from Forge scripts or tests.

[.contract-index]
.Functions
--
* {xref-Defender-Defender-deployContract-string-}[`++deployContract(contractName)++`]
* {xref-Defender-Defender-deployContract-string-struct-DefenderOptions-}[`++deployContract(contractName, defenderOpts)++`]
* {xref-Defender-Defender-deployContract-string-bytes-}[`++deployContract(contractName, constructorData)++`]
* {xref-Defender-Defender-deployContract-string-bytes-struct-DefenderOptions-}[`++deployContract(contractName, constructorData, defenderOpts)++`]
* {xref-Defender-Defender-proposeUpgrade-address-string-struct-Options-}[`++proposeUpgrade(proxyAddress, newImplementationContractName, opts)++`]
* {xref-Defender-Defender-getDeployApprovalProcess--}[`++getDeployApprovalProcess()++`]
* {xref-Defender-Defender-getUpgradeApprovalProcess--}[`++getUpgradeApprovalProcess()++`]

--

[.contract-item]
[[Defender-Defender-deployContract-string-]]
==== `[.contract-item-name]#++deployContract++#++(string contractName) → address++` [.item-kind]#internal#

Deploys a contract to the current network using OpenZeppelin Defender.

WARNING: Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe.

NOTE: If using an EOA or Safe to deploy, go to https://defender.openzeppelin.com/v2/#/deploy[Defender deploy] to submit the pending deployment while the script is running.
The script waits for the deployment to complete before it continues.

*Parameters:*

* `contractName` (`string`) - Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory
*Returns*

* (`address`) - Address of the deployed contract
[.contract-item]
[[Defender-Defender-deployContract-string-struct-DefenderOptions-]]
==== `[.contract-item-name]#++deployContract++#++(string contractName, struct DefenderOptions defenderOpts) → address++` [.item-kind]#internal#

Deploys a contract to the current network using OpenZeppelin Defender.

WARNING: Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe.

NOTE: If using an EOA or Safe to deploy, go to https://defender.openzeppelin.com/v2/#/deploy[Defender deploy] to submit the pending deployment while the script is running.
The script waits for the deployment to complete before it continues.

*Parameters:*

* `contractName` (`string`) - Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory
* `defenderOpts` (`struct DefenderOptions`) - Defender deployment options. Note that the `useDefenderDeploy` option is always treated as `true` when called from this function.
*Returns*

* (`address`) - Address of the deployed contract
[.contract-item]
[[Defender-Defender-deployContract-string-bytes-]]
==== `[.contract-item-name]#++deployContract++#++(string contractName, bytes constructorData) → address++` [.item-kind]#internal#

Deploys a contract with constructor arguments to the current network using OpenZeppelin Defender.

WARNING: Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe.

NOTE: If using an EOA or Safe to deploy, go to https://defender.openzeppelin.com/v2/#/deploy[Defender deploy] to submit the pending deployment while the script is running.
The script waits for the deployment to complete before it continues.

*Parameters:*

* `contractName` (`string`) - Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory
* `constructorData` (`bytes`) - Encoded constructor arguments
*Returns*

* (`address`) - Address of the deployed contract
[.contract-item]
[[Defender-Defender-deployContract-string-bytes-struct-DefenderOptions-]]
==== `[.contract-item-name]#++deployContract++#++(string contractName, bytes constructorData, struct DefenderOptions defenderOpts) → address++` [.item-kind]#internal#

Deploys a contract with constructor arguments to the current network using OpenZeppelin Defender.

WARNING: Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe.

NOTE: If using an EOA or Safe to deploy, go to https://defender.openzeppelin.com/v2/#/deploy[Defender deploy] to submit the pending deployment while the script is running.
The script waits for the deployment to complete before it continues.

*Parameters:*

* `contractName` (`string`) - Name of the contract to deploy, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory
* `constructorData` (`bytes`) - Encoded constructor arguments
* `defenderOpts` (`struct DefenderOptions`) - Defender deployment options. Note that the `useDefenderDeploy` option is always treated as `true` when called from this function.
*Returns*

* (`address`) - Address of the deployed contract
[.contract-item]
[[Defender-Defender-proposeUpgrade-address-string-struct-Options-]]
==== `[.contract-item-name]#++proposeUpgrade++#++(address proxyAddress, string newImplementationContractName, struct Options opts) → struct ProposeUpgradeResponse++` [.item-kind]#internal#

Proposes an upgrade to an upgradeable proxy using OpenZeppelin Defender.

This function validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract using Defender,
and proposes an upgrade to the new implementation contract using an upgrade approval process on Defender.

Supported for UUPS or Transparent proxies. Not currently supported for beacon proxies or beacons.
For beacons, use `Upgrades.prepareUpgrade` along with a transaction proposal on Defender to upgrade the beacon to the deployed implementation.

Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from <reference>` annotation.

WARNING: Ensure that the reference contract is the same as the current implementation contract that the proxy is pointing to.
This function does not validate that the reference contract is the current implementation.

NOTE: If using an EOA or Safe to deploy, go to https://defender.openzeppelin.com/v2/#/deploy[Defender deploy] to submit the pending deployment of the new implementation contract while the script is running.
The script waits for the deployment to complete before it continues.

*Parameters:*

* `proxyAddress` (`address`) - The proxy address
* `newImplementationContractName` (`string`) - Name of the new implementation contract to upgrade to, e.g. "MyContract.sol" or "MyContract.sol:MyContract" or artifact path relative to the project root directory
* `opts` (`struct Options`) - Common options. Note that the `defender.useDefenderDeploy` option is always treated as `true` when called from this function.
*Returns*

* (`struct ProposeUpgradeResponse`) - Struct containing the proposal ID and URL for the upgrade proposal
[.contract-item]
[[Defender-Defender-getDeployApprovalProcess--]]
==== `[.contract-item-name]#++getDeployApprovalProcess++#++() → struct ApprovalProcessResponse++` [.item-kind]#internal#

Gets the default deploy approval process configured for your deployment environment on OpenZeppelin Defender.

*Returns*

* (`struct ApprovalProcessResponse`) - Struct with the default deploy approval process ID and the associated address, such as a Relayer, EOA, or multisig wallet address.
[.contract-item]
[[Defender-Defender-getUpgradeApprovalProcess--]]
==== `[.contract-item-name]#++getUpgradeApprovalProcess++#++() → struct ApprovalProcessResponse++` [.item-kind]#internal#

Gets the default upgrade approval process configured for your deployment environment on OpenZeppelin Defender.
For example, this is useful for determining the default multisig wallet that you can use in your scripts to assign as the owner of your proxy.

*Returns*

* (`struct ApprovalProcessResponse`) - Struct with the default upgrade approval process ID and the associated address, such as a multisig or governor contract address.
Loading

0 comments on commit 372170b

Please sign in to comment.