Skip to content
Open
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
52 changes: 32 additions & 20 deletions .github/workflows/deploy-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ name: Deploy Contracts and Verify artifacts
on:
pull_request:
branches: [master]
paths: ["contracts/**", "scripts/**", "**.sol", "package.json", "bun.lock"]
paths:
["contracts/**", "scripts/**", "deployments/**", "**.sol", "package.json", "bun.lock"]
push:
branches: [master]
paths: ["contracts/**", "scripts/**", "**.sol", "package.json", "bun.lock"]
paths:
["contracts/**", "scripts/**", "deployments/**", "**.sol", "package.json", "bun.lock"]

permissions:
contents: read
Expand All @@ -23,12 +25,13 @@ jobs:

# Shared across steps. ACCOUNT_* is anvil test account 7 (public test keys,
# never valid on a real network) used to run the pipeline. FACTORY_DEPLOYER is
# the public address of the single-purpose factory key; PINNED_FACTORY is its
# nonce-0 CREATE address and the one recorded in every manifest. CANONICAL is
# the committed manifest that seeds DEPLOYMENTS.md; MANIFEST is the file this
# CI deploy writes. PRIVATE_KEY is deliberately absent here: it is set only on
# the first deploy to import the keystore, and must not reach the resume run,
# which reuses the already-imported account.
# the public address of the single-purpose factory key. CANONICAL is the
# committed manifest, the address set this deploy has to reproduce; MANIFEST is
# the file this CI deploy writes. PINNED_FACTORY is read out of CANONICAL after
# checkout rather than repeated here, so the expectation cannot drift from the
# manifest it is meant to describe. PRIVATE_KEY is deliberately absent: it is set
# only on the first deploy to import the keystore, and must not reach the resume
# run, which reuses the already-imported account.
env:
ACCOUNT_NAME: anvil-default-7
ACCOUNT_PASSWORD: anvil-default-password
Expand All @@ -38,7 +41,6 @@ jobs:
DOTNS_TLD: dot
WHITELIST_OPERATOR: "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"
FACTORY_DEPLOYER: "0xd498F7BC5bB3cBdd0068c3deEbbd814b69C3F164"
PINNED_FACTORY: "0x8533c79E058c5a6489CAFeCA86dc600E029D75f5"
CANONICAL: deployments/paseo-assethub/420420417.json
MANIFEST: deployments/paseo-local/420420420.json

Expand All @@ -49,6 +51,16 @@ jobs:
submodules: recursive
fetch-depth: 1

- name: Resolve the pinned factory address
run: |
FACTORY=$(jq -r '.Create3Factory // empty' "$CANONICAL")
if [[ ! "$FACTORY" =~ ^0x[0-9a-fA-F]{40}$ ]]; then
echo "::error::$CANONICAL has no usable Create3Factory entry."
exit 1
fi
echo "PINNED_FACTORY=$FACTORY" >> "$GITHUB_ENV"
echo "Expecting the factory at $FACTORY, per $CANONICAL."

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
Expand Down Expand Up @@ -267,10 +279,10 @@ jobs:
# This CI deploy reproduces the published address set. The canonical factory
# was deployed above, and every DotNS address is a pure function of that
# factory plus a fixed salt, so the freshly deployed manifest must equal the
# committed manifest that seeds DEPLOYMENTS.md. Assert that, print the
# expected-vs-actual table, then rerun the pipeline to prove the deploy is
# resumable: a rerun adopts every contract and still lands on the same set.
- name: Verify addresses match DEPLOYMENTS.md
# committed manifest. Assert that, print the expected-vs-actual table, then
# rerun the pipeline to prove the deploy is resumable: a rerun adopts every
# contract and still lands on the same set.
- name: Verify addresses match the committed manifest
id: verify_addresses
if: steps.verify.outcome == 'success'
run: |
Expand All @@ -279,7 +291,7 @@ jobs:
# Markdown expected-vs-actual table: expected = committed manifest,
# actual = this CI deployment. Any row whose addresses differ is MOVED.
emit_table() {
echo "### Deployed addresses vs DEPLOYMENTS.md"
echo "### Deployed addresses vs the committed manifest"
echo ""
echo "Expected is the committed manifest; actual is this CI deployment of the same pipeline."
echo ""
Expand Down Expand Up @@ -312,15 +324,15 @@ jobs:

if ! matches_canonical; then
{
echo "### Deployed addresses do not match DEPLOYMENTS.md"
echo "### Deployed addresses do not match the committed manifest"
echo ""
echo "The pipeline no longer reproduces the published set. Rows marked MOVED"
echo "below differ from the committed manifest; regenerate DEPLOYMENTS.md and"
echo "the manifest, or restore the salt or label that moved."
echo "below differ from the committed manifest; update it, or restore the"
echo "salt or label that moved."
echo ""
cat table.md
} > deploy-error.md
fail "Failed - addresses differ from DEPLOYMENTS.md"
fail "Failed - addresses differ from the committed manifest"
fi

# Resume: rerun the same pinned pipeline. It must adopt every contract
Expand All @@ -344,7 +356,7 @@ jobs:
{
echo "### Resume moved addresses"
echo ""
echo "The rerun no longer matches DEPLOYMENTS.md, so a resumed deploy would"
echo "The rerun no longer matches the committed manifest, so a resumed deploy would"
echo "relocate contracts."
echo ""
emit_table
Expand All @@ -355,7 +367,7 @@ jobs:
# Surface the table under the passing row too, so the address set is on
# record every run, not only on failure.
cp table.md deploy-error.md
echo "result=Reproduces DEPLOYMENTS.md; resume verified" >> "$GITHUB_OUTPUT"
echo "result=Reproduces the committed manifest; resume verified" >> "$GITHUB_OUTPUT"
echo "has_details=true" >> "$GITHUB_OUTPUT"

- name: Set final result
Expand Down
29 changes: 26 additions & 3 deletions .github/workflows/publish-prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,23 @@ jobs:
ls -1 release/abis | sort > release/expected-assets.txt
echo "Extracted $(wc -l < release/expected-assets.txt) ABIs"

# No addresses: a pre-release is cut to be deployed, so the recorded addresses still
# belong to the previous deployment of different code. Publishing them under this tag
# would break the promise that a release's addresses and ABIs come from the same release.
- name: Generate release manifest
run: |
bun scripts/js/release-metadata.mjs build --tag "$RELEASE_TAG" --out release \
--addresses false

- name: Package pre-release artifacts
run: |
TAG="$RELEASE_TAG"
cd release
zip -r "../dotns-abis-${TAG}.zip" abis/
zip -r "../dotns-abis-${TAG}.zip" abis/ release-manifest.json

- name: Generate release body
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="$RELEASE_TAG"
ASSET_BASE="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/download/$TAG"
Expand Down Expand Up @@ -179,6 +189,16 @@ jobs:
# substitution, which also leaves ${TAG} unexpanded. Substitute it here.
sed -i "s|\${ASSET_BASE}|$ASSET_BASE|g; s|\${TAG}|$TAG|g" release-body.md

{
echo ""
echo "### Deployments"
echo ""
echo "No addresses are published with a pre-release: this code is not deployed yet."
echo "Deploy this tag, then cut the release, which carries the addresses it produced."
echo ""
echo "- **Release contents:** [release-manifest.json]($ASSET_BASE/release-manifest.json)"
} >> release-body.md

- name: Create draft pre-release with artifacts
uses: softprops/action-gh-release@v2
with:
Expand All @@ -189,6 +209,7 @@ jobs:
files: |
dotns-abis-*.zip
release/abis/*.json
release/release-manifest.json
body_path: release-body.md
draft: true
prerelease: true
Expand All @@ -202,8 +223,10 @@ jobs:
TAG="$RELEASE_TAG"
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets \
--jq '.assets[].name' | sort > "$RUNNER_TEMP/actual-assets.txt"
{ cat release/expected-assets.txt; echo "dotns-abis-${TAG}.zip"; } \
| sort > "$RUNNER_TEMP/wanted-assets.txt"
{ cat release/expected-assets.txt
echo "dotns-abis-${TAG}.zip"
echo "release-manifest.json"
} | sort > "$RUNNER_TEMP/wanted-assets.txt"
if ! diff -u "$RUNNER_TEMP/wanted-assets.txt" "$RUNNER_TEMP/actual-assets.txt"; then
echo "::error::Draft pre-release $TAG does not match the expected asset set; delete the draft and re-run."
exit 1
Expand Down
47 changes: 44 additions & 3 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,21 @@ jobs:
ls -1 release/abis | sort > release/expected-assets.txt
echo "Extracted $(wc -l < release/expected-assets.txt) ABIs"

# Addresses come from the committed deployment manifests, the same files the
# deployment pipeline in dotns-releases measures a live deploy against. A consumer
# that only has the release needs them to reach any contract at all.
- name: Generate deployments and release manifest
run: bun scripts/js/release-metadata.mjs build --tag "$RELEASE_TAG" --out release

- name: Package release artifacts
run: |
TAG="$RELEASE_TAG"
cd release
zip -r "../dotns-abis-${TAG}.zip" abis/
zip -r "../dotns-abis-${TAG}.zip" abis/ deployments.json release-manifest.json

- name: Generate release body
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="$RELEASE_TAG"
ASSET_BASE="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/download/$TAG"
Expand Down Expand Up @@ -174,6 +182,34 @@ jobs:
# substitution, which also leaves ${TAG} unexpanded. Substitute it here.
sed -i "s|\${ASSET_BASE}|$ASSET_BASE|g; s|\${TAG}|$TAG|g" release-body.md

# Networks and chain ids only. The addresses stay in the asset, so the body
# cannot become another copy of them. Chain ids are not unique across networks,
# which is why the table is keyed by network.
{
echo ""
echo "### Deployments"
echo ""
echo "| Network | Chain id |"
echo "|---------|---------:|"
jq -r '.networks | to_entries[] | "| \(.key) | \(.value.chainId) |"' release/deployments.json
echo ""
echo "- **Addresses:** [deployments.json]($ASSET_BASE/deployments.json)"
echo "- **Release contents:** [release-manifest.json]($ASSET_BASE/release-manifest.json)"
} >> release-body.md

# `gh release view` with no tag resolves through /releases/latest, which skips
# drafts, so this is the previously published release and never the draft this run
# is about to create.
PREV=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName 2>/dev/null || true)
if [ -n "$PREV" ] && gh release download "$PREV" --repo "$GITHUB_REPOSITORY" \
--pattern deployments.json --dir prev >/dev/null 2>&1; then
bun scripts/js/release-metadata.mjs changelog --current release/deployments.json \
--previous prev/deployments.json --previous-tag "$PREV" >> release-body.md
else
bun scripts/js/release-metadata.mjs changelog \
--current release/deployments.json >> release-body.md
fi

- name: Create draft release with artifacts
uses: softprops/action-gh-release@v2
with:
Expand All @@ -184,6 +220,8 @@ jobs:
files: |
dotns-abis-*.zip
release/abis/*.json
release/deployments.json
release/release-manifest.json
body_path: release-body.md
draft: true
prerelease: false
Expand All @@ -197,8 +235,11 @@ jobs:
TAG="$RELEASE_TAG"
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets \
--jq '.assets[].name' | sort > "$RUNNER_TEMP/actual-assets.txt"
{ cat release/expected-assets.txt; echo "dotns-abis-${TAG}.zip"; } \
| sort > "$RUNNER_TEMP/wanted-assets.txt"
{ cat release/expected-assets.txt
echo "dotns-abis-${TAG}.zip"
echo "deployments.json"
echo "release-manifest.json"
} | sort > "$RUNNER_TEMP/wanted-assets.txt"
if ! diff -u "$RUNNER_TEMP/wanted-assets.txt" "$RUNNER_TEMP/actual-assets.txt"; then
echo "::error::Draft release $TAG does not match the expected asset set; delete the draft and re-run."
exit 1
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/release-metadata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Release Metadata

# The release generates deployments.json and release-manifest.json from the committed
# manifests and the contract list. Without this, a duplicate address, a missing
# Create3Factory or a malformed manifest would only surface when a release is cut. No
# contract build is needed: manifest checks read JSON, and listed contract names are
# resolved against sources.
on:
pull_request:
paths:
- "deployments/**"
- ".github/abi-contracts.txt"
- "scripts/js/release-metadata.mjs"
- ".github/workflows/release-metadata.yml"

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.2.6"
no-cache: true

- name: Validate deployment manifests and the contract list
run: bun scripts/js/release-metadata.mjs validate
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Contributing to DotNS

These guidelines apply to the DotNS repository ("dotns"). Contributions are welcome via issues, pull requests, reviews, and testing feedback. Protocol behaviour and the deployments table live in [README.md](./README.md); this file is the contributor mechanics.
These guidelines apply to the DotNS repository ("dotns"). Contributions are welcome via issues, pull requests, reviews, and testing feedback. Protocol behaviour is documented in [README.md](./README.md) and network addresses in `deployments/<network>/<chain-id>.json`; this file is the contributor mechanics.

## Types of contributing

Expand Down Expand Up @@ -269,8 +269,7 @@ forge test --no-match-path 'test/fork/**'
2. Delete the paired fork test under `test/fork/`.
3. Delete every `*Old.sol` and `I*Old.sol` referenced only by the upgrade script.
4. Delete temporary forge artefacts: `broadcast/<Script>.s.sol/` and `cache/<Script>.s.sol/`.
5. Update `deployments/<network>/<chainid>.json` with any new proxy addresses.
6. Update the README's deployments table with new proxy addresses (and any new EOA-registered keys).
5. Update `deployments/<network>/<chainid>.json` with any new addresses. It is the only place they are recorded, so nothing else needs editing.

## Code of conduct

Expand Down
Loading
Loading