Skip to content

Commit

Permalink
Generate documentation (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau authored Mar 8, 2024
1 parent f38a460 commit 3347b97
Show file tree
Hide file tree
Showing 16 changed files with 2,687 additions and 26 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ jobs:
- name: Run linter
run: yarn lint

docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup Node environment
uses: ./.github/actions/setup

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Check if docs are up to date
run: yarn docgen:test

tests:
strategy:
fail-fast: true
Expand Down
9 changes: 5 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ out/
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env

# Node modules
node_modules/
node_modules/

# Hardhat build output
artifacts/
cache_hardhat/
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Ensure that all tests pass. If you are adding new functionality, include testca

If linting errors or warnings occur, run `yarn lint:fix` to attempt to auto-fix issues. If there are remaining issues that cannot be auto-fixed, manually address them and re-run the command to ensure it passes.

### Updating documentation
```yarn docgen```

## Creating Pull Requests (PRs)

As a contributor, we ask that you fork this repository, work on your own fork and then submit pull requests. The pull requests will be reviewed and eventually merged into the main repo. See ["Fork-a-Repo"](https://help.github.com/articles/fork-a-repo/) for how this works.
21 changes: 21 additions & 0 deletions docs/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const path = require('path');
const fs = require('fs');

/** @type import('solidity-docgen/dist/config').UserConfig */
module.exports = {
outputDir: 'docs/modules/api/pages',
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;
}
}
},
};
696 changes: 696 additions & 0 deletions docs/modules/api/pages/api-foundry-upgrades.adoc

Large diffs are not rendered by default.

156 changes: 156 additions & 0 deletions docs/modules/pages/foundry-defender.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
= OpenZeppelin Defender integration

OpenZeppelin Foundry Upgrades can be used for performing deployments through https://docs.openzeppelin.com/defender/[OpenZeppelin Defender], which allows for features such as gas pricing estimation, resubmissions, and automated bytecode and source code verification.

WARNING: Defender deployments are **always** broadcast to a live network, regardless of whether you are using the `broadcast` cheatcode.
The recommended pattern is to separate Defender scripts from scripts that rely on network forking and simulations, to avoid mixing simulation and live network data.

== Installation

See xref:foundry-upgrades#installion[Using with Foundry - Installation].

== Prerequisites
1. Install https://nodejs.org/[Node.js].

2. Configure your `foundry.toml` to include build info and storage layout:
[source,toml]
----
[profile.default]
build_info = true
extra_output = ["storageLayout"]
----

NOTE: Metadata must also be included in the compiler output, which it is by default.

3. Include `--ffi` in your `forge script` or `forge test` command.

4. Set the following environment variables in your `.env` file at your project root, using your Team API key and secret from OpenZeppelin Defender:
[source]
----
DEFENDER_KEY=<Your API key>
DEFENDER_SECRET<Your API secret>
----

== Usage

=== Upgradeable Contracts

If you are deploying upgradeable contracts, use the `Upgrades` library as described in xref:foundry-upgrades#installion[Using with Foundry - Installation] but set the option `defender.useDefenderDeploy = true` when calling functions to cause all deployments to occur through OpenZeppelin Defender.

**Example 1 - Deploying a proxy**:
To deploy a UUPS proxy, create a script called `Defender.s.sol` like the following:
[source,solidity]
----
pragma solidity ^0.8.20;
import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
import {Defender, ApprovalProcessResponse} from "openzeppelin-foundry-upgrades/Defender.sol";
import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {MyContract} from "../src/MyContract.sol";
contract DefenderScript is Script {
function setUp() public {}
function run() public {
ApprovalProcessResponse memory upgradeApprovalProcess = Defender.getUpgradeApprovalProcess();
if (upgradeApprovalProcess.via == address(0)) {
revert(string.concat("Upgrade approval process with id ", upgradeApprovalProcess.approvalProcessId, " has no assigned address"));
}
Options memory opts;
opts.defender.useDefenderDeploy = true;
address proxy = Upgrades.deployUUPSProxy(
"MyContract.sol",
abi.encodeCall(MyContract.initialize, ("Hello World", upgradeApprovalProcess.via)),
opts
);
console.log("Deployed proxy to address", proxy);
}
}
----

Then run the following command:
[source,console]
----
forge script <path to the script you created above> --ffi --rpc-url <RPC URL for the network you want to use>
----

The above example assumes the implementation contract takes an initial owner address as an argument for its `initialize` function. The script retrieves the address associated with the upgrade approval process configured in Defender (such as a multisig address), and uses that address as the initial owner so that it can have upgrade rights for the proxy.

This example calls the `Upgrades.deployUUPSProxy` function with the `defender.useDefenderDeploy` option to deploy both the implementation contract and a UUPS proxy to the connected network using Defender. The function waits for the deployments to complete, which may take a few minutes per contract, then returns with the deployed proxy address. While the function is waiting, you can monitor your deployment status in OpenZeppelin Defender's https://defender.openzeppelin.com/v2/#/deploy[Deploy module].

NOTE: If using an EOA or Safe to deploy, you must submit the pending deployments in Defender while the script is running. The script waits for each deployment to complete before it continues.

**Example 2 - Proposing an upgrade to a proxy**:
To propose an upgrade through Defender, create a script like the following:
[source,solidity]
----
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
import {MyContractV2} from "../src/MyContractV2.sol";
import {ProposeUpgradeResponse, Defender, Options} from "openzeppelin-foundry-upgrades/Defender.sol";
contract DefenderScript is Script {
function setUp() public {}
function run() public {
Options memory opts;
ProposeUpgradeResponse memory response = Defender.proposeUpgrade(
<MY_PROXY_ADDRESS>,
"MyContractV2.sol",
opts
);
console.log("Proposal id", response.proposalId);
console.log("Url", response.url);
}
}
----

Then run the script as in Example 1, and go the resulting URL to review and approve the upgrade proposal.

=== Non-Upgradeable Contracts

If you are deploying non-upgradeable contracts, import the `Defender` library from `Defender.sol` and use its functions to deploy contracts through OpenZeppelin Defender.

**Example:**

To deploy a non-upgradeable contract, create a script called `Defender.s.sol` like the following:
[source,solidity]
----
pragma solidity ^0.8.20;
import {Script} from "forge-std/Script.sol";
import {console} from "forge-std/console.sol";
import {Defender} from "openzeppelin-foundry-upgrades/Defender.sol";
contract DefenderScript is Script {
function setUp() public {}
function run() public {
address deployed = Defender.deployContract("MyContract.sol", abi.encode("arguments for the constructor"));
console.log("Deployed contract to address", deployed);
}
}
----

Then run the following command:
[source,console]
----
forge script <path to the script you created above> --ffi --rpc-url <RPC URL for the network you want to use>
----

The above example calls the `Defender.deployContract` function to deploy the specified contract to the connected network using Defender. The function waits for the deployment to complete, which may take a few minutes, then returns with the deployed contract address. While the function is waiting, you can monitor your deployment status in OpenZeppelin Defender's https://defender.openzeppelin.com/v2/#/deploy[Deploy module].

NOTE: If using an EOA or Safe to deploy, you must submit the pending deployment in Defender while the script is running. The script waits for the deployment to complete before it continues.
158 changes: 158 additions & 0 deletions docs/modules/pages/foundry-upgrades.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
= Using with Foundry

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

== Installation

Run these commands:
[source,console]
----
forge install OpenZeppelin/openzeppelin-foundry-upgrades
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
----

Set the following in `remappings.txt`, replacing any previous definitions of these remappings:
[source]
----
@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
----

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

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 https://gitforwindows.org/[Git for Windows], add the following line in the `.env` file of your project (using forward slashes):
[source]
----
OPENZEPPELIN_BASH_PATH="C:/Program Files/Git/bin/bash"
----

== Version Limitations

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

== Before Running

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

If you want to be able to run upgrade safety checks, the following are needed:

1. Install https://nodejs.org/[Node.js].

2. Configure your `foundry.toml` to include build info and storage layout:
[source,toml]
----
[profile.default]
build_info = true
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 https://docs.openzeppelin.com/upgrades-plugins/1.x/api-core#define-reference-contracts[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.

5. Include `--ffi` in your `forge script` or `forge test` command.

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.

== Usage

Import the library in your Foundry scripts or tests:
[source,solidity]
----
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
----

Then call functions from `Upgrades.sol` to run validations, deployments, or upgrades.

=== Examples

Deploy a UUPS proxy:
[source,solidity]
----
address proxy = Upgrades.deployUUPSProxy(
"MyContract.sol",
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
----

Deploy a transparent proxy:
[source,solidity]
----
address proxy = Upgrades.deployTransparentProxy(
"MyContract.sol",
INITIAL_OWNER_ADDRESS_FOR_PROXY_ADMIN,
abi.encodeCall(MyContract.initialize, ("arguments for the initialize function"))
);
----

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 transparent or UUPS proxy and call an arbitrary function (such as a reinitializer) during the upgrade process:
[source,solidity]
----
Upgrades.upgradeProxy(
transparentProxy,
"MyContractV2.sol",
abi.encodeCall(MyContractV2.foo, ("arguments for foo"))
);
----

Upgrade a transparent or UUPS proxy without calling any additional function:
[source,solidity]
----
Upgrades.upgradeProxy(
transparentProxy,
"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:
[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:
[source,solidity]
----
address beacon = Upgrades.deployBeacon("MyContract.sol", INITIAL_OWNER_ADDRESS_FOR_BEACON);
----

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

Upgrade a beacon:
[source,solidity]
----
Upgrades.upgradeBeacon(beacon, "MyContractV2.sol");
----

=== 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.

IMPORTANT: Include the `--sender <ADDRESS>` flag for the `forge script` command when performing upgrades, specifying an address that owns the proxy or proxy admin. Otherwise, `OwnableUnauthorizedAccount` errors will occur.

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 xref:api-foundry-upgrades.adoc[Foundry Upgrades API] for the full API documentation.
Loading

0 comments on commit 3347b97

Please sign in to comment.