Skip to content

Commit

Permalink
Add referenceBuildInfoDir and exclude options (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau authored Sep 24, 2024
1 parent b3b285b commit 16e0ae2
Show file tree
Hide file tree
Showing 27 changed files with 440 additions and 78 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ node_modules/
artifacts/
cache_hardhat/

test_artifacts/

# Soldeer dry-run output
openzeppelin-foundry-upgrades.zip
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.3.6 (2024-09-24)

- Add `referenceBuildInfoDir` and `exclude` options. ([#74](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/74))

## 0.3.5 (2024-09-20)

### Potentially breaking changes
Expand Down
57 changes: 37 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@ import {MyToken} from "src/MyToken.sol";

Then call functions from the imported library to run validations, deployments, or upgrades.

### Examples
## Examples

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

### Deploy a proxy

Deploy a UUPS proxy:
```solidity
address proxy = Upgrades.deployUUPSProxy(
Expand All @@ -138,12 +140,26 @@ address proxy = Upgrades.deployTransparentProxy(
);
```

Deploy an upgradeable beacon and a beacon proxy:
```solidity
address beacon = Upgrades.deployBeacon("MyContract.sol", INITIAL_OWNER_ADDRESS_FOR_BEACON);
address proxy = Upgrades.deployBeaconProxy(
beacon,
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
```

### Use your contract

Call your contract's functions as normal, but remember to always use the proxy address:
```solidity
MyContract instance = MyContract(proxy);
instance.myFunction();
```

### Upgrade a proxy or beacon

Upgrade a transparent or UUPS proxy and call an arbitrary function (such as a reinitializer) during the upgrade process:
```solidity
Upgrades.upgradeProxy(
Expand All @@ -162,6 +178,11 @@ Upgrades.upgradeProxy(
);
```

Upgrade a beacon:
```solidity
Upgrades.upgradeBeacon(beacon, "MyContractV2.sol");
```

> **Warning**
> When upgrading a proxy or beacon, ensure that the new contract either has its `@custom:oz-upgrades-from <reference>` annotation set to the current implementation contract used by the proxy or beacon, or set it with the `referenceContract` option, for example:
> ```solidity
Expand All @@ -171,25 +192,17 @@ Upgrades.upgradeProxy(
> // or Upgrades.upgradeBeacon(beacon, "MyContractV2.sol", opts);
> ```
Deploy an upgradeable beacon:
```solidity
address beacon = Upgrades.deployBeacon("MyContract.sol", INITIAL_OWNER_ADDRESS_FOR_BEACON);
```

Deploy a beacon proxy:
```solidity
address proxy = Upgrades.deployBeaconProxy(
beacon,
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
```

Upgrade a beacon:
```solidity
Upgrades.upgradeBeacon(beacon, "MyContractV2.sol");
```
> **Tip**
> If possible, keep the old version of the implementation contract's source code somewhere in your project to use as a reference as above. This requires the new version to be in a different directory, Solidity file, or using a different contract name. Otherwise, if you want to use the same directory and name for the new version, keep the build info directory from the previous deployment (or build it from an older branch of your project repository) and reference it as follows:
> ```solidity
> Options memory opts;
> opts.referenceBuildInfoDir = "/old-builds/build-info-v1";
> opts.referenceContract = "build-info-v1:MyContract";
> Upgrades.upgradeProxy(proxy, "MyContract.sol", "", opts);
> // or Upgrades.upgradeBeacon(beacon, "MyContract.sol", opts);
> ```
### Coverage Testing
## 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
Expand All @@ -203,7 +216,7 @@ address proxy = Upgrades.deployUUPSProxy(
> **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
## 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 All @@ -212,3 +225,7 @@ Run your script with `forge script` to broadcast and deploy. See Foundry's [Soli
> **Note**
> Include the `--verify` flag for the `forge script` command if you want to verify source code such as on Etherscan. This will verify your implementation contracts along with any proxy contracts as part of the deployment.
## API

See [Foundry Upgrades API](https://docs.openzeppelin.com/upgrades-plugins/api-foundry-upgrades) for the full API documentation.
2 changes: 2 additions & 0 deletions docs/modules/api/pages/Options.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import { Options } from "openzeppelin-foundry-upgrades/Options.sol";
```solidity
struct Options {
string referenceContract;
string referenceBuildInfoDir;
bytes constructorData;
string[] exclude;
string unsafeAllow;
bool unsafeAllowRenames;
bool unsafeSkipProxyAdminCheck;
Expand Down
34 changes: 34 additions & 0 deletions docs/modules/api/pages/api-foundry-upgrades.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
= OpenZeppelin Foundry Upgrades API

== Contract name formats

Contract names must be provided in specific formats depending on context. The following are the required formats for each context:

=== Foundry artifact format

Contexts:

* `contractName` parameter
* `referenceContract` option if `referenceBuildInfoDir` option is not set

Can be in any of the following forms according to Foundry's https://book.getfoundry.sh/cheatcodes/get-code[getCode] cheatcode:

* the Solidity file name, e.g. `ContractV1.sol`
* the Solidity file name and the contract name, e.g. `ContractV1.sol:ContractV1`
* the artifact path relative to the project root directory, e.g. `out/ContractV1.sol/ContractV1.json`

=== Annotation format

Contexts:

* `@custom:oz-upgrades-from <reference>` annotation
* `referenceContract` option if `referenceBuildInfoDir` option is set

Can be in any of the following forms according to the https://docs.openzeppelin.com/upgrades-plugins/api-core#define-reference-contracts[OpenZeppelin Upgrades CLI]:

* the contract name, e.g. `ContractV1`
* fully qualified contract name, e.g. `contracts/tokens/ContractV1.sol:ContractV1`

If the `referenceBuildInfoDir` option is set, include the build info directory short name as a prefix, resulting in one of the following forms:

* the build info directory short name and the contract name, e.g. `build-info-v1:ContractV1`
* the build info directory short name and the fully qualified contract name, e.g. `build-info-v1:contracts/tokens/ContractV1.sol:ContractV1`

== Common Options

The following options can be used with some of the below functions. See https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/blob/main/src/Options.sol[Options.sol] for detailed descriptions of each option.
Expand Down
54 changes: 33 additions & 21 deletions docs/modules/pages/foundry-upgrades.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ import {MyToken} from "src/MyToken.sol";

Then call functions from the imported library to run validations, deployments, or upgrades.

=== Examples
== Examples

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

=== Deploy a proxy

Deploy a UUPS proxy:
[source,solidity]
----
Expand All @@ -156,13 +158,28 @@ address proxy = Upgrades.deployTransparentProxy(
);
----

Deploy an upgradeable beacon and a beacon proxy:
[source,solidity]
----
address beacon = Upgrades.deployBeacon("MyContract.sol", INITIAL_OWNER_ADDRESS_FOR_BEACON);
address proxy = Upgrades.deployBeaconProxy(
beacon,
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
----

=== Use your contract

Call your contract's functions as normal, but remember to always use the proxy address:
[source,solidity]
----
MyContract instance = MyContract(proxy);
instance.myFunction();
----

=== Upgrade a proxy or beacon

Upgrade a transparent or UUPS proxy and call an arbitrary function (such as a reinitializer) during the upgrade process:
[source,solidity]
----
Expand All @@ -183,37 +200,32 @@ Upgrades.upgradeProxy(
);
----

WARNING: When upgrading a proxy or beacon, ensure that the new contract either has its `@custom:oz-upgrades-from <reference>` annotation set to the current implementation contract used by the proxy or beacon, or set it with the `referenceContract` option, for example:
[source,solidity]
----
Options memory opts;
opts.referenceContract = "MyContractV1.sol";
Upgrades.upgradeProxy(proxy, "MyContractV2.sol", "", opts);
// or Upgrades.upgradeBeacon(beacon, "MyContractV2.sol", opts);
----

Deploy an upgradeable beacon:
Upgrade a beacon:
[source,solidity]
----
address beacon = Upgrades.deployBeacon("MyContract.sol", INITIAL_OWNER_ADDRESS_FOR_BEACON);
Upgrades.upgradeBeacon(beacon, "MyContractV2.sol");
----

Deploy a beacon proxy:
WARNING: When upgrading a proxy or beacon, ensure that the new contract either has its `@custom:oz-upgrades-from <reference>` annotation set to the name of the old implementation contract used by the proxy or beacon, or set it with the `referenceContract` option, for example:
[source,solidity]
----
address proxy = Upgrades.deployBeaconProxy(
beacon,
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
Options memory opts;
opts.referenceContract = "MyContractV1.sol";
Upgrades.upgradeProxy(proxy, "MyContractV2.sol", "", opts);
// or Upgrades.upgradeBeacon(beacon, "MyContractV2.sol", opts);
----

Upgrade a beacon:
TIP: If possible, keep the old version of the implementation contract's source code somewhere in your project to use as a reference as above. This requires the new version to be in a different directory, Solidity file, or using a different contract name. Otherwise, if you want to use the same directory and name for the new version, keep the build info directory from the previous deployment (or build it from an older branch of your project repository) and reference it as follows:
[source,solidity]
----
Upgrades.upgradeBeacon(beacon, "MyContractV2.sol");
Options memory opts;
opts.referenceBuildInfoDir = "/old-builds/build-info-v1";
opts.referenceContract = "build-info-v1:MyContract";
Upgrades.upgradeProxy(proxy, "MyContract.sol", "", opts);
// or Upgrades.upgradeBeacon(beacon, "MyContract.sol", opts);
----

=== Coverage Testing
== 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
Expand All @@ -226,7 +238,7 @@ address proxy = Upgrades.deployUUPSProxy(

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
== Deploying and Verifying

Run your script with `forge script` to broadcast and deploy. See Foundry's https://book.getfoundry.sh/tutorials/solidity-scripting[Solidity Scripting] guide.

Expand Down
18 changes: 17 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,20 @@ test = "test-profiles/openzeppelin-contracts-v4-with-v5-proxies/test"
remappings = [
"@openzeppelin/contracts/=node_modules/@openzeppelin/contracts",
"@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable-v4"
]
]

[profile.build-info-v1]
src = "test-profiles/build-info-v1/src"
test = "test-profiles/build-info-v1/test"

[profile.build-info-v2]
src = "test-profiles/build-info-v2/src"
test = "test-profiles/build-info-v2/test"

[profile.build-info-v2-bad]
src = "test-profiles/build-info-v2-bad/src"
test = "test-profiles/build-info-v2-bad/test"

[profile.build-info-v2-reference-contract]
src = "test-profiles/build-info-v2-reference-contract/src"
test = "test-profiles/build-info-v2-reference-contract/test"
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
"scripts": {
"clean": "forge clean && hardhat clean",
"compile": "forge build",
"test": "npm run forge:test && npm run forge:script && npm run forge:test-v4 && npm run forge:script-v4 && npm run forge:test-v4-with-v5-proxies && npm run forge:script-v4-with-v5-proxies",
"test": "npm run forge:test && npm run forge:script && npm run forge:test-v4 && npm run forge:script-v4 && npm run forge:test-v4-with-v5-proxies && npm run forge:script-v4-with-v5-proxies && npm run test-reference-builds",
"forge:test": "FOUNDRY_PROFILE=default forge test -vvv --ffi --force",
"forge:script": "FOUNDRY_PROFILE=default forge script test/Upgrades.s.sol --ffi --force",
"forge:test-v4": "FOUNDRY_PROFILE=openzeppelin-contracts-v4 forge test -vvv --ffi --force --use solc:0.8.2",
"forge:script-v4": "FOUNDRY_PROFILE=openzeppelin-contracts-v4 forge script test-profiles/openzeppelin-contracts-v4/test/LegacyUpgrades.s.sol --ffi --force --use solc:0.8.2",
"forge:test-v4-with-v5-proxies": "FOUNDRY_PROFILE=openzeppelin-contracts-v4-with-v5-proxies forge test -vvv --ffi --force",
"forge:script-v4-with-v5-proxies": "FOUNDRY_PROFILE=openzeppelin-contracts-v4-with-v5-proxies forge script test-profiles/openzeppelin-contracts-v4-with-v5-proxies/test/Upgrades.s.sol --ffi --force",
"test-reference-builds": "bash scripts/test-reference-builds.sh",
"lint": "prettier --log-level warn --ignore-path .gitignore '{src,test}/**/*.sol' --check && solhint 'src/**/*.sol'",
"lint:fix": "prettier --log-level warn --ignore-path .gitignore '{src,test}/**/*.sol' --write",
"docgen": "hardhat clean && hardhat compile && hardhat docgen",
Expand All @@ -24,7 +25,7 @@
"@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@^v4.9.6",
"@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@^v4.9.6",
"@openzeppelin/defender-deploy-client-cli": "0.0.1-alpha.9",
"@openzeppelin/upgrades-core": "^1.32.3",
"@openzeppelin/upgrades-core": "^1.37.0",
"hardhat": "^2.21.0",
"prettier": "^3.0.0",
"prettier-plugin-solidity": "^1.1.0",
Expand Down
19 changes: 19 additions & 0 deletions scripts/test-reference-builds.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash

set -euo pipefail

export FOUNDRY_PROFILE=build-info-v1
forge build --force

rm -rf test_artifacts
mkdir -p test_artifacts/build-info-v1
mv out/build-info/*.json test_artifacts/build-info-v1

export FOUNDRY_PROFILE=build-info-v2
forge test -vvv --ffi --force

export FOUNDRY_PROFILE=build-info-v2-bad
forge test -vvv --ffi --force

export FOUNDRY_PROFILE=build-info-v2-reference-contract
forge test -vvv --ffi --force
25 changes: 24 additions & 1 deletion src/Options.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,39 @@ pragma solidity ^0.8.0;
*/
struct Options {
/*
* The reference contract to use for storage layout comparisons, e.g. "ContractV1.sol" or "ContractV1.sol:ContractV1".
* The reference contract to use for storage layout comparisons.
*
* For supported formats, see https://docs.openzeppelin.com/upgrades-plugins/api-foundry-upgrades#contract_name_formats
*
* If not using the `referenceBuildInfoDir` option, this must be in Foundry artifact format.
*
* If using the `referenceBuildInfoDir` option, this must be in annotation format prefixed with the build info directory short name.
* For example, if `referenceBuildInfoDir` is `previous-builds/build-info-v1` and the reference contract name is `ContractV1`,
* then set this to `build-info-v1:ContractV1`
*
* If not set, attempts to use the `@custom:oz-upgrades-from <reference>` annotation from the contract.
*/
string referenceContract;
/*
* Absolute or relative path to a build info directory from a previous version of the project to use for storage layout comparisons.
* Relative paths must be relative to the Foundry project root.
*
* When using this option, refer to this directory using prefix `<dirName>:` before the contract name or fully qualified name
* in the `referenceContract` option or `@custom:oz-upgrades-from` annotation, where `<dirName>` is the directory short name.
* The directory short name must be unique when compared to the main build info directory.
*/
string referenceBuildInfoDir;
/*
* Encoded constructor arguments for the implementation contract.
* Note that these are different from initializer arguments, and will be used in the deployment of the implementation contract itself.
* Can be used to initialize immutable variables.
*/
bytes constructorData;
/*
* Exclude validations for contracts in source file paths that match any of the given glob patterns.
* For example, patterns such as "contracts/helpers/*.sol". Does not apply to reference contracts.
*/
string[] exclude;
/*
* Selectively disable one or more validation errors. Comma-separated list that must be compatible with the
* --unsafeAllow option described in https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core#usage
Expand Down
Loading

0 comments on commit 16e0ae2

Please sign in to comment.