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
82 changes: 82 additions & 0 deletions .github/workflows/genesis-build-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Genesis Build Smoke

# Runs the release-time genesis build on PRs, with the same secrets and the same command,
# so a bad secret or a broken pipeline fails here instead of on master when a release is
# cut. The extractor's unit tests and `bash -n` cannot catch either — v0.5.6-rc1 died on a
# key `cast` could not decode, after every PR check had passed.
#
# Skipped on fork PRs: secrets are withheld there.
on:
pull_request:
branches: [master]
paths:
- "scripts/genesis/**"
- "scripts/deploy/**"
- "contracts/**"
- "**.sol"
- "deployments/**"
- "foundry.toml"
- ".github/workflows/publish-release.yml"
- ".github/workflows/publish-prerelease.yml"
- ".github/workflows/genesis-build-smoke.yml"
- ".github/actions/setup-foundry/action.yml"
push:
branches: [master]
paths:
- "scripts/genesis/**"
- ".github/workflows/genesis-build-smoke.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: genesis-smoke-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
genesis-build-smoke:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- uses: ./.github/actions/setup-foundry

- uses: actions/setup-node@v4
with:
node-version: "20"

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

# Not optional: bun's postinstall runs setup.bash, which lands lib/ dependencies on
# their pinned SHAs. Without it the build compiles against stale OpenZeppelin and
# fails on override/mutability mismatches — the release workflow always runs this.
- run: bun install

# The same secrets and command as the release step. The parity check inside the
# script runs for real: with the real factory key the addresses must equal the
# committed manifest, so nothing is skipped or stubbed.
- name: Build the genesis exactly as a release does
env:
FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
FACTORY_DEPLOYER_KEY: ${{ secrets.FACTORY_DEPLOYER_KEY }}
DOTNS_ADMIN_KEY: ${{ secrets.DOTNS_ADMIN_KEY }}
DOTNS_TLD: test
run: bash scripts/genesis/build-genesis.sh release

- name: Assert the artifact shape
run: |
test -s release/dotns-genesis-test.json
jq -e '.tld == "test" and (.accounts | length > 0)' release/dotns-genesis-test.json >/dev/null
echo "accounts: $(jq '.accounts | length' release/dotns-genesis-test.json)"

- uses: actions/upload-artifact@v4
with:
name: dotns-genesis-test
path: release/dotns-genesis-test.json
1 change: 0 additions & 1 deletion .github/workflows/publish-prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ jobs:
FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
FACTORY_DEPLOYER_KEY: ${{ secrets.FACTORY_DEPLOYER_KEY }}
DOTNS_ADMIN_KEY: ${{ secrets.DOTNS_ADMIN_KEY }}
DOTNS_ADMIN_MNEMONIC: ${{ secrets.DOTNS_ADMIN_MNEMONIC }}
run: bash scripts/genesis/build-genesis.sh release

- name: Extract ABIs
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,13 @@ jobs:
# contracts, the deploy scripts and the CREATE3 factory key that fixes every address,
# and the artifact then ships from the same commit as the ABIs beside it.
#
# The owner key is taken from DOTNS_ADMIN_KEY if set, otherwise derived from
# DOTNS_ADMIN_MNEMONIC. It ends up owning the registry, resolvers, registrar, store factory
# and beacons in the genesis storage, so the script refuses to run without one.
# DOTNS_ADMIN_KEY (a raw private key, the only accepted credential) ends up owning the
# registry, resolvers, registrar, store factory and beacons in the genesis storage.
- name: Build pallet-revive genesis
env:
FOUNDRY_DISABLE_NIGHTLY_WARNING: "1"
FACTORY_DEPLOYER_KEY: ${{ secrets.FACTORY_DEPLOYER_KEY }}
DOTNS_ADMIN_KEY: ${{ secrets.DOTNS_ADMIN_KEY }}
DOTNS_ADMIN_MNEMONIC: ${{ secrets.DOTNS_ADMIN_MNEMONIC }}
run: bash scripts/genesis/build-genesis.sh release

- name: Extract ABIs
Expand Down
61 changes: 36 additions & 25 deletions scripts/genesis/build-genesis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,16 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOYMENT_FILE="deployments/localhost/31337.json"
CANONICAL_MANIFEST="deployments/paseo-assethub/420420417.json"

# Who OWNS the contracts in the genesis state (REQUIRED, one of the three below).
# Who OWNS the contracts in the genesis state: DOTNS_ADMIN_KEY, a raw private key,
# REQUIRED. No DotNS *address* depends on it — with CREATE3 the addresses are a pure
# function of the factory — but every ownership and role assignment written into
# genesis storage does.
#
# No DotNS *address* depends on this key — with CREATE3 the addresses are a pure
# function of the factory (see FACTORY_DEPLOYER_KEY below, which owns only the
# factory) — but every ownership and role assignment written into genesis storage
# does: this key ends up owning the registry, the resolvers, the registrar, the
# store factory and the beacons.
#
# Accepted, in order of precedence:
# DOTNS_ADMIN_KEY a raw private key — the admin credential this repo already holds
# DOTNS_ADMIN_MNEMONIC the admin mnemonic; index $DOTNS_ADMIN_INDEX (default 0)
#
# Deliberately NOT accepted: DOTNS_MNEMONIC. That is the operational credential the
# whitelist workflows drive the `dotns` CLI with, not the contract admin, and quietly
# making it the owner of every contract in a genesis would be a hard mistake to spot.
# Not DEPLOYER_KEY: that name is dotns-releases' own secret, and accepting it here
# would make which key owns a published genesis depend on which repo the build ran in.
# The ONLY accepted credential. No mnemonic, no fallback: with two credentials the
# build would guess which account owns everything, and nothing downstream would
# notice a wrong guess — the parity check validates addresses, not owners.
# Not DOTNS_MNEMONIC (the whitelist workflows' operational credential) and not
# DEPLOYER_KEY (dotns-releases' own secret) for the same reason.
ADMIN_KEY="${DOTNS_ADMIN_KEY:-}"

# Single-purpose CREATE3 factory deployer key (REQUIRED). Every DotNS address is
Expand Down Expand Up @@ -90,18 +83,12 @@ for tool in forge anvil cast node jq curl; do
command -v "$tool" >/dev/null 2>&1 || { echo "Error: $tool is not on PATH" >&2; exit 1; }
done

# Needs cast, so it happens after the check above.
if [ -z "$ADMIN_KEY" ] && [ -n "${DOTNS_ADMIN_MNEMONIC:-}" ]; then
ADMIN_KEY="$(cast wallet private-key --mnemonic "$DOTNS_ADMIN_MNEMONIC" "${DOTNS_ADMIN_INDEX:-0}")"
echo "Owner key derived from DOTNS_ADMIN_MNEMONIC, index ${DOTNS_ADMIN_INDEX:-0}."
fi

if [ -z "$ADMIN_KEY" ]; then
cat >&2 <<'MSG'
Error: no owner key. Set DOTNS_ADMIN_KEY or DOTNS_ADMIN_MNEMONIC.
Error: DOTNS_ADMIN_KEY is required.

Whichever is given becomes the owner of every DotNS contract in the genesis
state, so this build refuses to fall back to a public dev key.
It becomes the owner of every DotNS contract in the genesis state, so this
build refuses to fall back to a public dev key or any other credential.
MSG
exit 1
fi
Expand All @@ -116,6 +103,30 @@ MSG
exit 1
fi

# ---- Key shape preflight ----
# `cast` rejects a malformed key with a bare "Failed to decode private key" — no variable
# name, no shape — and a GitHub secret cannot be read back to inspect. Describe the problem
# (length, word count) without ever printing the value. Surrounding whitespace is stripped:
# a pasted trailing newline is the classic way a secret breaks.
require_hex_key() {
local name="$1" raw="${2-}"
if printf '%s' "$raw" | tr -d '[:space:]' | grep -Eq '^(0x)?[0-9a-fA-F]{64}$'; then
return 0
fi
local words; words=$(printf '%s' "$raw" | wc -w | tr -d ' ')
echo "Error: $name is not a raw private key (need 64 hex chars, 0x optional):" >&2
echo " got ${#raw} char(s), $words word(s)." >&2
if [ "$words" -ge 12 ]; then
echo " That shape is a mnemonic. This build takes only a key — derive one with" >&2
echo " \`cast wallet private-key --mnemonic '<...>'\` and store the result." >&2
fi
exit 1
}
require_hex_key DOTNS_ADMIN_KEY "$ADMIN_KEY"
ADMIN_KEY="$(printf '%s' "$ADMIN_KEY" | tr -d '[:space:]')"
require_hex_key FACTORY_DEPLOYER_KEY "$FACTORY_DEPLOYER_KEY"
FACTORY_DEPLOYER_KEY="$(printf '%s' "$FACTORY_DEPLOYER_KEY" | tr -d '[:space:]')"

DEPLOYER_ADDR="$(cast wallet address --private-key "$ADMIN_KEY")"
export WHITELIST_OPERATOR="${WHITELIST_OPERATOR:-$DEPLOYER_ADDR}"
echo "Contract owner: $DEPLOYER_ADDR"
Expand Down
Loading