feat: factor out all scripts and add ci for repo#30
Conversation
There was a problem hiding this comment.
Pull request overview
This PR factors previously inlined workflow logic into reusable Bash scripts, adds a scripts test suite + CI workflow, and updates reusable workflows to fetch/run the shared scripts from the BreadchainCoop/etherform repository.
Changes:
- Added modular scripts for deploy helpers, coverage parsing/threshold checks, and upgrade safety validation.
- Added Bash-based unit tests and fixtures for the scripts plus a GitHub Actions workflow to run ShellCheck + tests.
- Updated reusable workflows to checkout etherform scripts via
etherform-refand execute them instead of duplicating inline shell logic.
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test-resolve-network.sh | Adds jq-level tests for network resolution fixtures. |
| tests/test-prepare-env.sh | Adds tests for env normalization and exporting. |
| tests/test-parse-broadcast.sh | Adds tests for broadcast parsing and summary generation. |
| tests/test-extract-summary.sh | Adds tests for coverage parsing + markdown output. |
| tests/test-deployment-summary.sh | Adds tests for deployment step summary formatting. |
| tests/test-check-threshold.sh | Adds tests for threshold pass/fail logic. |
| tests/fixtures/deploy-networks.json | Fixture for chain/network mapping. |
| tests/fixtures/coverage-raw.txt | Fixture for forge coverage output parsing. |
| tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json | Fixture for Foundry broadcast parsing. |
| scripts/upgrade-safety/validate.sh | New script implementing upgrade safety validation formerly in workflows. |
| scripts/deploy/verify-blockscout.sh | New script to submit + poll contract verification on Blockscout. |
| scripts/deploy/resolve-network.sh | New script to resolve Blockscout URL/name and validate API reachability. |
| scripts/deploy/prepare-env.sh | New (sourceable) script for PRIVATE_KEY normalization + env exports. |
| scripts/deploy/parse-broadcast.sh | New script to find broadcast, extract chain id, and write summary file. |
| scripts/deploy/deployment-summary.sh | New script to emit deployment markdown to GitHub step summary. |
| scripts/coverage/extract-summary.sh | New script to parse coverage-raw.txt and produce outputs + markdown. |
| scripts/coverage/check-threshold.sh | New script to fail CI if coverage drops below threshold. |
| README.md | Documents etherform-ref and new scripts layout/testing. |
| .github/workflows/test.yml | Adds CI job to run ShellCheck + script unit tests. |
| .github/workflows/_upgrade-safety.yml | Switches workflow to checkout etherform scripts and call validate script. |
| .github/workflows/_foundry-cicd.yml | Replaces inline deploy/coverage/upgrade logic with checked-out scripts. |
| .github/workflows/_deploy-testnet.yml | Replaces inline deploy logic with checked-out scripts. |
| .github/workflows/_ci.yml | Replaces inline coverage logic with checked-out scripts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| echo "::warning::No Blockscout URL found for chain ID $CHAIN_ID in $NETWORK_CONFIG. Falling back to testnets[0]." | ||
| BLOCKSCOUT_URL=$(jq -r '.testnets[0].blockscout_url' "$NETWORK_CONFIG") | ||
| fi | ||
|
|
There was a problem hiding this comment.
After the fallback, BLOCKSCOUT_URL can still be empty/null (e.g., missing/empty testnets, missing key). That currently surfaces later as a confusing API error (e.g., null/api/). Add a post-fallback validation (non-empty and ^https?://) and emit a targeted error like “No valid blockscout_url found in ”.
| # Post-fallback validation: ensure we have a non-empty, HTTP(S) Blockscout URL | |
| if [[ -z "$BLOCKSCOUT_URL" || "$BLOCKSCOUT_URL" == "null" || ! "$BLOCKSCOUT_URL" =~ ^https?:// ]]; then | |
| echo "::error::No valid blockscout_url found in $NETWORK_CONFIG" | |
| exit 1 | |
| fi |
| if [[ -n "${DEPLOY_ENV_VARS:-}" ]]; then | ||
| while IFS= read -r line; do | ||
| [[ -z "$line" || "$line" == \#* ]] && continue | ||
| export "${line?}" |
There was a problem hiding this comment.
Directly export \"${line?}\" executes arbitrary assignments from DEPLOY_ENV_VARS without validating the key. A malformed line (or a line starting with -) can cause export errors and halt the calling workflow (since this is sourced). Prefer parsing KEY=VALUE, validating KEY against a safe pattern (e.g., ^[A-Za-z_][A-Za-z0-9_]*$), and then exporting only that key/value.
| export "${line?}" | |
| # Expect KEY=VALUE; skip malformed lines | |
| if [[ "$line" != *=* ]]; then | |
| continue | |
| fi | |
| key=${line%%=*} | |
| value=${line#*=} | |
| # Validate KEY against a safe variable-name pattern | |
| if [[ ! "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then | |
| continue | |
| fi | |
| export "$key=$value" |
| echo "::warning::Failed to build base branch — contracts without explicit reference will be validated for upgradeability only" | ||
| fi | ||
| else | ||
| echo "::warning::Could not checkout base branch '$BASE_BRANCH' — contracts without explicit reference will be validated for upgradeability only" |
There was a problem hiding this comment.
If git worktree add fails, BASE_DIR remains a plain temp directory and git worktree remove won’t delete it (and errors are suppressed), leaving leaked temp dirs on the runner. Track whether the worktree was successfully added, and rm -rf \"$BASE_DIR\" in the failure path (and/or after attempting git worktree remove).
| echo "::warning::Could not checkout base branch '$BASE_BRANCH' — contracts without explicit reference will be validated for upgradeability only" | |
| echo "::warning::Could not checkout base branch '$BASE_BRANCH' — contracts without explicit reference will be validated for upgradeability only" | |
| rm -rf "$BASE_DIR" |
|
|
||
| # Clean up worktree | ||
| if [[ -n "$BASE_DIR" ]]; then | ||
| git worktree remove "$BASE_DIR" --force 2>/dev/null || true |
There was a problem hiding this comment.
If git worktree add fails, BASE_DIR remains a plain temp directory and git worktree remove won’t delete it (and errors are suppressed), leaving leaked temp dirs on the runner. Track whether the worktree was successfully added, and rm -rf \"$BASE_DIR\" in the failure path (and/or after attempting git worktree remove).
| git worktree remove "$BASE_DIR" --force 2>/dev/null || true | |
| git worktree remove "$BASE_DIR" --force 2>/dev/null || true | |
| rm -rf "$BASE_DIR" 2>/dev/null || true |
| # Validate each contract | ||
| while IFS= read -r entry; do | ||
| CONTRACT=$(echo "$entry" | jq -r '.contract') | ||
| REF_VALUE=$(echo "$entry" | jq -c '.reference // empty') |
There was a problem hiding this comment.
The repo adds a scripts test harness, but scripts/upgrade-safety/validate.sh isn’t covered. Given its branching behavior (missing config, empty contracts, base-branch worktree build, explicit reference handling), add at least smoke tests using small fixture configs and stubbing git/forge/npx via PATH to validate the decision logic and emitted step-summary output.
| | `run-halmos` | boolean | `false` | Run Halmos symbolic execution | | ||
| | `etherform-ref` | string | `'main'` | Git ref for etherform scripts checkout | | ||
|
|
||
| | Secret | Required | Description | |
There was a problem hiding this comment.
etherform-ref is documented under the “Secret” table here, but it’s an input (non-secret) in the workflows. Update the README so etherform-ref appears only in the inputs section(s), and avoid listing it as a secret to prevent confusion for workflow consumers.
| | `node-version` | string | `'20'` | Node.js version for package installation | | ||
| | `upgrades-config` | string | `'.github/upgrades.json'` | Path to upgrade safety config | | ||
| | `base-branch` | string | `'main'` | Base branch for upgrade safety comparison | | ||
| | `etherform-ref` | string | `'main'` | Git ref for etherform scripts checkout | |
There was a problem hiding this comment.
etherform-ref is documented under the “Secret” table here, but it’s an input (non-secret) in the workflows. Update the README so etherform-ref appears only in the inputs section(s), and avoid listing it as a secret to prevent confusion for workflow consumers.
- Add post-fallback validation for BLOCKSCOUT_URL in resolve-network.sh - Validate KEY=VALUE format and key names in prepare-env.sh export - Add curl timeouts and normalize URL in verify-blockscout.sh status poll
- Hard fail when base branch build fails (opt-in ALLOW_FALLBACK=true) - Pin @openzeppelin/upgrades-core version (default 1.44.2) - Require upgrade-safety success for deploy (no longer allows skipped) - Quote deploy-script input to prevent flag injection - Fail on empty contracts array when base branch has entries - Remove duplicate Node.js setup steps in both workflows
- validate.sh: fail hard when base branch fetch/read fails during empty-contracts check (gated by ALLOW_FALLBACK=true) - verify-blockscout.sh: normalize BLOCKSCOUT_URL once and use consistently for both submission and polling - test-prepare-env.sh: add tests for malformed lines, invalid key names, and values containing equals signs
…dening-fixes fix: address PR #32 review comments
fix: harden deploy scripts and upgrade safety workflows
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 24 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (1)
.github/workflows/_foundry-cicd.yml:88
- The
workflow_callinputrun-upgrade-safetywas removed, which is a breaking change for any repos currently passing that input (they’ll get an “Unexpected input” workflow error). If disabling upgrade-safety is still a supported use case, consider reintroducing the input (even if deprecated) or documenting the breaking change prominently.
default: 0
# Upgrade Safety Options
upgrades-config:
description: 'Path to upgrade safety configuration (upgrades.json)'
type: string
default: '.github/upgrades.json'
main-branch:
description: 'Base branch for upgrade safety comparison'
type: string
default: 'main'
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@rubydusa is this ready for review ? |
Yes |
RonTuretzky
left a comment
There was a problem hiding this comment.
@hudsonhrh give this a review when you get a chance 👍
hudsonhrh
left a comment
There was a problem hiding this comment.
@rubydusa @RonTuretzky thoughts here?
| etherform-ref: | ||
| description: 'Git ref for etherform scripts checkout (default: main)' | ||
| type: string | ||
| default: 'main' |
There was a problem hiding this comment.
should we recommend main by default or a specific commit? specific commit is safer so if we merge something to main they have to chose to upgrade so we can't rug them or something
There was a problem hiding this comment.
make a backlog issue and address in a future release
There was a problem hiding this comment.
should we recommend main by default or a specific commit? specific commit is safer so if we merge something to main they have to chose to upgrade so we can't rug them or something
ideally we'll have releases with tags or something, if you look at other github actions they usually have something like a vX appended
Opening an issue
No description provided.