diff --git a/.github/workflows/upgrade-facets.yml b/.github/workflows/upgrade-facets.yml
index c9967d91..f174100d 100644
--- a/.github/workflows/upgrade-facets.yml
+++ b/.github/workflows/upgrade-facets.yml
@@ -14,7 +14,7 @@ on:
- arbitrumSepolia
- arbitrum
default: 'arbitrumSepolia'
- dry_run:
+ dry-run:
description: 'Dry Run (fork test only, no actual deployment)'
required: false
type: boolean
@@ -27,13 +27,12 @@ jobs:
upgrade:
needs: pre-upgrade
runs-on: ubuntu-latest
- environment: ${{ inputs.network }} # Use the selected environment
+ environment: ${{ inputs.network }}
permissions:
contents: write # Required to commit artifacts.
pull-requests: write # Required to create pull requests.
env:
UPGRADE_SCRIPT: 'v6.1.0-bulk-processing.ts' # Update this for each specific upgrade.
- # For commit action
COMMIT_MESSAGE: 'chore: Save upgrade artifacts - ${{ inputs.network }} (runId:${{ github.run_id }})'
GHA_BOT_NAME: 'GitHub Actions Bot'
GHA_BOT_EMAIL: 'github-actions[bot]@users.noreply.github.com'
@@ -54,7 +53,7 @@ jobs:
run: npm run build
- name: Run fork test (dry run)
- if: inputs.dry_run == true
+ if: inputs.dry-run == true
env:
# Note: it is required to define both private key env variables when calling Hardhat.
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
@@ -66,10 +65,10 @@ jobs:
elif [ "${{ inputs.network }}" == "arbitrum" ]; then
export ARBITRUM_FORK=true
fi
- npx hardhat run scripts/upgrades/${{ env.UPGRADE_SCRIPT }} --network hardhat
+ UPGRADE_SCRIPT=${{ env.UPGRADE_SCRIPT }} bash ./scripts/upgrades/dry-run.sh
- name: Execute upgrade on live network
- if: inputs.dry_run == false
+ if: inputs.dry-run == false
env:
# Note: it is required to define both private key env variables when calling Hardhat.
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
@@ -80,7 +79,7 @@ jobs:
npx hardhat run scripts/upgrades/${{ env.UPGRADE_SCRIPT }} --network ${{ inputs.network }}
- name: Push artifacts to the current branch
- if: inputs.dry_run == false && github.ref != 'refs/heads/main'
+ if: inputs.dry-run == false && github.ref != 'refs/heads/main'
uses: stefanzweifel/git-auto-commit-action@v5
with:
file_pattern: |
@@ -92,7 +91,7 @@ jobs:
# Since the `main` branch is protected, create a PR to push artifacts.
- name: Push artifacts through a pull request
- if: inputs.dry_run == false && github.ref == 'refs/heads/main'
+ if: inputs.dry-run == false && github.ref == 'refs/heads/main'
uses: peter-evans/create-pull-request@v7
with:
add-paths: |
diff --git a/.gitignore b/.gitignore
index c4772434..c0d9e792 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,7 +15,8 @@ cache
artifacts
contracts/hardhat-dependency-compiler/
-deployments/*hardhat*
+deployments/hardhat
+deployments/external-hardhat
deployments/dev-*
# Flattened Solidity file used to generate diagrams.
diff --git a/scripts/upgrades/README.md b/scripts/upgrades/README.md
index 8ca2fd71..fe786ee7 100644
--- a/scripts/upgrades/README.md
+++ b/scripts/upgrades/README.md
@@ -1,7 +1,7 @@
# PoCo Smart Contracts Upgrade Guide
-This document explains the recommended steps for creating and applying
-a safe, traceable, and reproducible upgrade to the PoCo Diamond proxy.
+This document explains the recommended steps for creating and applying a safe, traceable,
+and reproducible upgrade to the PoCo Diamond proxy.
## Upgrade Steps
@@ -9,31 +9,33 @@ a safe, traceable, and reproducible upgrade to the PoCo Diamond proxy.
Run the full test suite to make sure everything is working before starting an upgrade.
1. **Create a new upgrade script**:
- Name the script using the version and upgrade name format: `vX.Y.Z-upgrade-name.ts`.
+ Name the script using the version and upgrade name in the form `vX.Y.Z-upgrade-name.ts`
+ and implement the upgrade logic.
2. **Create a corresponding Markdown report**:
- Copy the template file `v0.0.0-template.md` and rename it to match the script: `vX.Y.Z-upgrade-name.md`.
+ Copy the template file `v0.0.0-template.md` and rename it to match the script's name
+ (`vX.Y.Z-upgrade-name.md`).
-3. **Update GitHub Actions**:
+3. **Test dry-runs locally**:
+ Use the script [./dry-run.sh](./dry-run.sh) and check the logs.
+
+4. **Update GitHub Actions**:
Modify `upgrade-facets.yml` workflow to call the new upgrade script.
-4. **Upgrade on Testnet**:
+5. **Upgrade on Testnet**:
- ⚠️ Always upgrade on the testnet first.
- Trigger the upgrade workflow on GitHub and choose the testnet network.
- Start with a **dry run** to simulate the upgrade.
- Once verified, apply the upgrade on the live testnet.
-5. **Upgrade on Mainnet**:
+6. **Upgrade on Mainnet**:
- Trigger the upgrade workflow on GitHub and choose the mainnet network.
- Perform a dry run first.
- Apply the upgrade on the mainnet.
- Merge the artifacts PR after successful execution.
-6. **Update upgrade report**:
+7. **Update upgrade report**:
Fill in all required information in `vX.Y.Z-upgrade-name.ts` (tx hashes, logs, ...).
-7. **Create a release**
+8. **Create a release**
- Use **Release Please** to tag the upgrade version and create the release on GitHub.
-
-
-Following these steps ensures upgrades are **safe, traceable, and reproducible**.
diff --git a/scripts/upgrades/dry-run.sh b/scripts/upgrades/dry-run.sh
new file mode 100644
index 00000000..39b2c9a9
--- /dev/null
+++ b/scripts/upgrades/dry-run.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+set -e
+
+# Usage:
+# ARBITRUM_SEPOLIA_FORK=true \
+# UPGRADE_SCRIPT= \
+# bash ./scripts/upgrades/dry-run.sh
+
+
+DEPLOYMENTS_FOLDER=''
+if [ "${ARBITRUM_SEPOLIA_FORK}" == "true" ]; then
+ DEPLOYMENTS_FOLDER=arbitrumSepolia
+elif [ "${ARBITRUM_FORK}" == "true" ]; then
+ DEPLOYMENTS_FOLDER=arbitrum
+fi
+if [ -z "${DEPLOYMENTS_FOLDER}" ]; then
+ echo "Error: You must set either ARBITRUM_SEPOLIA_FORK=true or ARBITRUM_FORK=true."
+ exit 1
+fi
+# Copy the forked network deployments to the hardhat network folder.
+rm -rf deployments/hardhat
+cp -r deployments/${DEPLOYMENTS_FOLDER} deployments/hardhat
+# Stage the old deployments to have a clean diff after the upgrade script run.
+cp .gitignore .gitignore.bak
+sed -i.bak '/deployments\/hardhat/d' .gitignore
+git add deployments/hardhat
+# Run the upgrade and print the git diff.
+npx hardhat run scripts/upgrades/${UPGRADE_SCRIPT} --network hardhat
+echo "\n=== Upgrade diff ==="
+git --no-pager diff --name-status
+mv .gitignore.bak .gitignore
diff --git a/scripts/upgrades/v6.1.0-bulk-processing.ts b/scripts/upgrades/v6.1.0-bulk-processing.ts
index 188aca09..883b2df0 100644
--- a/scripts/upgrades/v6.1.0-bulk-processing.ts
+++ b/scripts/upgrades/v6.1.0-bulk-processing.ts
@@ -9,6 +9,7 @@ import {
getUpgradeContext,
linkFacetsToDiamond,
printOnchainProxyFunctions,
+ removeDanglingFacetDeploymentArtifacts,
removeFacetsFromDiamond,
removeFunctionsFromDiamond,
} from '../../utils/proxy-tools';
@@ -103,6 +104,7 @@ async function main() {
await linkFacetsToDiamond(proxyAddress, proxyOwner, facetsToAdd);
await printOnchainProxyFunctions(proxyAddress);
console.log('Upgrade performed successfully!');
+ await removeDanglingFacetDeploymentArtifacts(proxyAddress);
await tryVerify(facetsToAdd.map((facet) => facet.name));
}
diff --git a/utils/proxy-tools.ts b/utils/proxy-tools.ts
index 2ac253f5..f12fae65 100644
--- a/utils/proxy-tools.ts
+++ b/utils/proxy-tools.ts
@@ -3,7 +3,7 @@
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
import { ContractFactory, FunctionFragment, Interface, ZeroAddress } from 'ethers';
-import { ethers } from 'hardhat';
+import { deployments, ethers } from 'hardhat';
import { FacetCut, FacetCutAction } from 'hardhat-deploy/dist/types';
import type { IDiamond } from '../typechain';
import {
@@ -279,7 +279,7 @@ export async function removeFacetsFromDiamond(
throw new Error(`Facet ${facet.name} is empty or does not exist on-chain`);
}
console.log(
- `Will remove the whole facet ${facet.name} [address: ${facet.address}, functions:${selectors.length}]`,
+ `Will remove facet [name:${facet.name}, address: ${facet.address}, functions:${selectors.length}]`,
);
facetCuts.push({
facetAddress: ZeroAddress,
@@ -350,3 +350,25 @@ export async function removeFunctionsFromDiamond(
await tx.wait();
console.log('Functions removed successfully!');
}
+
+/**
+ * Removes dangling deployment artifacts for facets that are no longer linked to the diamond proxy.
+ * This is not done automatically in `removeFacetsFromDiamond` because we deploy new facets first,
+ * then remove old facets, which sometimes overwrites the existing ones.
+ * @param proxyAddress address of the diamond proxy
+ */
+export async function removeDanglingFacetDeploymentArtifacts(proxyAddress: string) {
+ console.log('\n=== Removing dangling deployment artifacts ===');
+ const allDeployments = await deployments.all();
+ const diamondLoupe = DiamondLoupeFacet__factory.connect(proxyAddress, ethers.provider);
+ const onchainFacets = await diamondLoupe.facetAddresses();
+ for (const deploymentName of Object.keys(allDeployments)) {
+ const deploymentAddress = allDeployments[deploymentName].address;
+ if (deploymentName.endsWith('Facet') && !onchainFacets.includes(deploymentAddress)) {
+ console.log(
+ `Deleting dangling facet artifact [name:${deploymentName}, address:${deploymentAddress}]`,
+ );
+ await deployments.delete(deploymentName);
+ }
+ }
+}