From 3084c1e4b04693702495e768024f873d135c2140 Mon Sep 17 00:00:00 2001 From: rubydusa Date: Thu, 30 Apr 2026 13:07:39 -0400 Subject: [PATCH] feat: include constructor args and method calls in deployment summary Closes #17. The deployment summary now reads run-latest.json directly and emits constructor arguments per deployed contract plus a collapsible section listing every post-deploy CALL with its function signature and arguments. - deployment-summary.sh: switched from deployment-summary.txt to BROADCAST_FILE, renders constructor args column and Method Calls
block (omitted when no CALLs). - parse-broadcast.sh: dropped the now-unused deployment-summary.txt write. - Workflows: pass BROADCAST_FILE through to the summary step. - Tests + fixture: enriched broadcast fixture with arguments/function fields; expanded deployment-summary tests; trimmed parse-broadcast test for the removed file. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/_deploy-testnet.yml | 1 + .github/workflows/_foundry-cicd.yml | 1 + scripts/deploy/deployment-summary.sh | 62 +++++++++-- scripts/deploy/parse-broadcast.sh | 4 +- .../Deploy.s.sol/31337/run-latest.json | 12 ++- tests/test-deployment-summary.sh | 102 +++++++++++++++--- tests/test-parse-broadcast.sh | 11 +- 7 files changed, 156 insertions(+), 37 deletions(-) diff --git a/.github/workflows/_deploy-testnet.yml b/.github/workflows/_deploy-testnet.yml index 4299ec6..2a9049a 100644 --- a/.github/workflows/_deploy-testnet.yml +++ b/.github/workflows/_deploy-testnet.yml @@ -137,6 +137,7 @@ jobs: - name: Create deployment summary env: BLOCKSCOUT_URL: ${{ steps.network.outputs.blockscout_url }} + BROADCAST_FILE: ${{ steps.parse.outputs.broadcast_file }} SUMMARY_TITLE: Testnet Deployment Summary run: bash .etherform/scripts/deploy/deployment-summary.sh diff --git a/.github/workflows/_foundry-cicd.yml b/.github/workflows/_foundry-cicd.yml index e88d73c..fd7712f 100644 --- a/.github/workflows/_foundry-cicd.yml +++ b/.github/workflows/_foundry-cicd.yml @@ -522,6 +522,7 @@ jobs: - name: Create deployment summary env: BLOCKSCOUT_URL: ${{ steps.network.outputs.blockscout_url }} + BROADCAST_FILE: ${{ steps.parse.outputs.broadcast_file }} SUMMARY_TITLE: Testnet Deployment Summary run: bash .etherform/scripts/deploy/deployment-summary.sh diff --git a/scripts/deploy/deployment-summary.sh b/scripts/deploy/deployment-summary.sh index 24c8d81..f004b79 100755 --- a/scripts/deploy/deployment-summary.sh +++ b/scripts/deploy/deployment-summary.sh @@ -1,26 +1,66 @@ #!/usr/bin/env bash set -euo pipefail -# Write a deployment summary table to $GITHUB_STEP_SUMMARY. +# Write a deployment summary to $GITHUB_STEP_SUMMARY from a Foundry +# broadcast run-latest.json file: deployed contracts (with constructor args) +# and post-deploy method calls. # # Env inputs: # BLOCKSCOUT_URL — required +# BROADCAST_FILE — required, path to run-latest.json # SUMMARY_TITLE — optional, defaults to "Deployment Summary" -# -# File inputs: -# deployment-summary.txt must exist in the working directory : "${BLOCKSCOUT_URL:?BLOCKSCOUT_URL is required}" +: "${BROADCAST_FILE:?BROADCAST_FILE is required}" +: "${GITHUB_STEP_SUMMARY:?GITHUB_STEP_SUMMARY is required}" TITLE="${SUMMARY_TITLE:-Deployment Summary}" +if [[ ! -f "$BROADCAST_FILE" ]]; then + echo "::error::BROADCAST_FILE not found: $BROADCAST_FILE" + exit 1 +fi + +# Format a JSON arguments array (read from stdin) into a markdown-friendly cell. +# Pipe characters are escaped so they don't break the table layout. +format_args() { + jq -r ' + if . == null or . == [] then "_(none)_" + else (map("`" + (. | tostring) + "`") | join(", ")) + end + ' | sed 's/|/\\|/g' +} + { echo "## $TITLE" echo "" - echo "| Contract | Address | Explorer |" - echo "|----------|---------|----------|" + echo "| Contract | Address | Constructor Args | Explorer |" + echo "|----------|---------|------------------|----------|" + while IFS=$'\t' read -r CONTRACT ADDRESS ARGS_JSON; do + ARGS=$(printf '%s' "$ARGS_JSON" | format_args) + echo "| ${CONTRACT:-?} | \`$ADDRESS\` | $ARGS | [View](${BLOCKSCOUT_URL}/address/$ADDRESS) |" + done < <(jq -r ' + .transactions[] + | select(.transactionType == "CREATE" or .transactionType == "CREATE2") + | [(.contractName // ""), .contractAddress, (.arguments // [] | tojson)] + | @tsv + ' "$BROADCAST_FILE") - while read -r line; do - CONTRACT=$(echo "$line" | cut -d: -f1) - ADDRESS=$(echo "$line" | cut -d: -f2 | tr -d ' ') - echo "| $CONTRACT | \`$ADDRESS\` | [View](${BLOCKSCOUT_URL}/address/$ADDRESS) |" - done < deployment-summary.txt + CALL_COUNT=$(jq '[.transactions[] | select(.transactionType == "CALL")] | length' "$BROADCAST_FILE") + if [[ "$CALL_COUNT" -gt 0 ]]; then + echo "" + echo "
" + echo "Method Calls ($CALL_COUNT)" + echo "" + echo "| Contract | Address | Method | Arguments |" + echo "|----------|---------|--------|-----------|" + while IFS=$'\t' read -r CONTRACT ADDRESS FUNCTION ARGS_JSON; do + ARGS=$(printf '%s' "$ARGS_JSON" | format_args) + echo "| ${CONTRACT:-?} | \`$ADDRESS\` | \`${FUNCTION:-?}\` | $ARGS |" + done < <(jq -r ' + .transactions[] + | select(.transactionType == "CALL") + | [(.contractName // ""), .contractAddress, (.function // ""), (.arguments // [] | tojson)] + | @tsv + ' "$BROADCAST_FILE") + echo "
" + fi } >> "$GITHUB_STEP_SUMMARY" diff --git a/scripts/deploy/parse-broadcast.sh b/scripts/deploy/parse-broadcast.sh index c9ec4d4..eed7aca 100755 --- a/scripts/deploy/parse-broadcast.sh +++ b/scripts/deploy/parse-broadcast.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail # Parse Foundry broadcast artifacts after deployment. -# Finds run-latest.json, extracts chain ID, writes deployment-summary.txt. +# Finds run-latest.json and extracts the chain ID. # # Outputs (via $GITHUB_OUTPUT): # broadcast_file — path to the broadcast JSON @@ -19,4 +19,4 @@ CHAIN_ID=$(echo "$BROADCAST_FILE" | sed -E 's|.*/([0-9]+)/run-latest\.json$|\1|' echo "chain_id=$CHAIN_ID" >> "$GITHUB_OUTPUT" echo "Deployed to chain ID: $CHAIN_ID" -jq -r '.transactions[] | select(.transactionType == "CREATE") | "\(.contractName): \(.contractAddress)"' "$BROADCAST_FILE" | tee deployment-summary.txt +jq -r '.transactions[] | select(.transactionType == "CREATE") | "\(.contractName): \(.contractAddress)"' "$BROADCAST_FILE" diff --git a/tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json b/tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json index ba7346f..6d7ec44 100644 --- a/tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json +++ b/tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json @@ -4,19 +4,25 @@ "hash": "0xabc123", "transactionType": "CREATE", "contractName": "MyToken", - "contractAddress": "0x1234567890abcdef1234567890abcdef12345678" + "contractAddress": "0x1234567890abcdef1234567890abcdef12345678", + "function": null, + "arguments": ["0xAdminAdminAdminAdminAdminAdminAdminAdmin0001"] }, { "hash": "0xdef456", "transactionType": "CREATE", "contractName": "MyProxy", - "contractAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + "contractAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "function": null, + "arguments": null }, { "hash": "0x789012", "transactionType": "CALL", "contractName": "MyProxy", - "contractAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + "contractAddress": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd", + "function": "initialize(address,uint256)", + "arguments": ["0x1234567890abcdef1234567890abcdef12345678", "1000000000000000000"] } ] } diff --git a/tests/test-deployment-summary.sh b/tests/test-deployment-summary.sh index ce1706d..79befa9 100755 --- a/tests/test-deployment-summary.sh +++ b/tests/test-deployment-summary.sh @@ -1,56 +1,128 @@ #!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +FIXTURE="$SCRIPT_DIR/tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json" FAILURES=0 echo "=== Testing scripts/deploy/deployment-summary.sh ===" -# Test 1: Generates markdown table +# Test 1: Generates deployed-contracts table with constructor args + explorer ( TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT - cd "$TMPDIR" - - cat > deployment-summary.txt <<'EOF' -MyToken: 0x1234567890abcdef1234567890abcdef12345678 -MyProxy: 0xabcdefabcdefabcdefabcdefabcdefabcdefabcd -EOF export BLOCKSCOUT_URL="https://eth-sepolia.blockscout.com" + export BROADCAST_FILE="$FIXTURE" export GITHUB_STEP_SUMMARY="$TMPDIR/summary.md" touch "$GITHUB_STEP_SUMMARY" bash "$SCRIPT_DIR/scripts/deploy/deployment-summary.sh" grep -q "Deployment Summary" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: no title"; exit 1; } + grep -q "Constructor Args" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: header missing"; exit 1; } grep -q "MyToken" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: MyToken not found"; exit 1; } grep -q "MyProxy" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: MyProxy not found"; exit 1; } - grep -q "View" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: explorer link not found"; exit 1; } + grep -q "0xAdminAdminAdminAdminAdminAdminAdminAdmin0001" "$GITHUB_STEP_SUMMARY" \ + || { echo "FAIL: constructor arg not rendered"; exit 1; } + grep -q "_(none)_" "$GITHUB_STEP_SUMMARY" \ + || { echo "FAIL: empty-args placeholder not rendered"; exit 1; } + grep -q "View" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: explorer link missing"; exit 1; } + + echo "PASS: deployed-contracts table renders" +) || { FAILURES=$((FAILURES + 1)); } + +# Test 2: Method calls section appears with function + args +( + TMPDIR=$(mktemp -d) + trap 'rm -rf "$TMPDIR"' EXIT + + export BLOCKSCOUT_URL="https://eth-sepolia.blockscout.com" + export BROADCAST_FILE="$FIXTURE" + export GITHUB_STEP_SUMMARY="$TMPDIR/summary.md" + touch "$GITHUB_STEP_SUMMARY" + + bash "$SCRIPT_DIR/scripts/deploy/deployment-summary.sh" + + grep -q "
" "$GITHUB_STEP_SUMMARY" || { echo "FAIL:
missing"; exit 1; } + grep -q "Method Calls" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: Method Calls header missing"; exit 1; } + grep -q "initialize(address,uint256)" "$GITHUB_STEP_SUMMARY" \ + || { echo "FAIL: function signature missing"; exit 1; } + grep -q "1000000000000000000" "$GITHUB_STEP_SUMMARY" \ + || { echo "FAIL: call argument missing"; exit 1; } - echo "PASS: generates markdown table" + echo "PASS: method calls section renders" ) || { FAILURES=$((FAILURES + 1)); } -# Test 2: Custom title via SUMMARY_TITLE +# Test 3: Method calls section is omitted when there are no CALLs ( TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT - cd "$TMPDIR" - cat > deployment-summary.txt <<'EOF' -MyToken: 0x1234567890abcdef1234567890abcdef12345678 + cat > "$TMPDIR/run-latest.json" <<'EOF' +{ + "transactions": [ + { + "transactionType": "CREATE", + "contractName": "OnlyContract", + "contractAddress": "0x1111111111111111111111111111111111111111", + "arguments": null + } + ] +} EOF export BLOCKSCOUT_URL="https://eth-sepolia.blockscout.com" + export BROADCAST_FILE="$TMPDIR/run-latest.json" + export GITHUB_STEP_SUMMARY="$TMPDIR/summary.md" + touch "$GITHUB_STEP_SUMMARY" + + bash "$SCRIPT_DIR/scripts/deploy/deployment-summary.sh" + + grep -q "OnlyContract" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: contract row missing"; exit 1; } + if grep -q "Method Calls" "$GITHUB_STEP_SUMMARY"; then + echo "FAIL: Method Calls section should be omitted when no CALLs" + exit 1 + fi + + echo "PASS: omits method calls section when none" +) || { FAILURES=$((FAILURES + 1)); } + +# Test 4: Custom title via SUMMARY_TITLE +( + TMPDIR=$(mktemp -d) + trap 'rm -rf "$TMPDIR"' EXIT + + export BLOCKSCOUT_URL="https://eth-sepolia.blockscout.com" + export BROADCAST_FILE="$FIXTURE" export SUMMARY_TITLE="Testnet Deployment Summary" export GITHUB_STEP_SUMMARY="$TMPDIR/summary.md" touch "$GITHUB_STEP_SUMMARY" bash "$SCRIPT_DIR/scripts/deploy/deployment-summary.sh" - grep -q "Testnet Deployment Summary" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: custom title not found"; exit 1; } + grep -q "Testnet Deployment Summary" "$GITHUB_STEP_SUMMARY" \ + || { echo "FAIL: custom title not found"; exit 1; } echo "PASS: custom title works" ) || { FAILURES=$((FAILURES + 1)); } -echo "--- $((2 - FAILURES))/2 tests passed ---" +# Test 5: Fails when BROADCAST_FILE is missing +( + TMPDIR=$(mktemp -d) + trap 'rm -rf "$TMPDIR"' EXIT + + export BLOCKSCOUT_URL="https://eth-sepolia.blockscout.com" + export BROADCAST_FILE="$TMPDIR/does-not-exist.json" + export GITHUB_STEP_SUMMARY="$TMPDIR/summary.md" + touch "$GITHUB_STEP_SUMMARY" + + if bash "$SCRIPT_DIR/scripts/deploy/deployment-summary.sh" > /dev/null 2>&1; then + echo "FAIL: should have exited with error" + exit 1 + fi + + echo "PASS: fails on missing broadcast file" +) || { FAILURES=$((FAILURES + 1)); } + +echo "--- $((5 - FAILURES))/5 tests passed ---" exit $FAILURES diff --git a/tests/test-parse-broadcast.sh b/tests/test-parse-broadcast.sh index 40a4b7e..83eacbc 100755 --- a/tests/test-parse-broadcast.sh +++ b/tests/test-parse-broadcast.sh @@ -23,7 +23,7 @@ echo "=== Testing scripts/deploy/parse-broadcast.sh ===" echo "PASS: extracts broadcast_file and chain_id" ) || { FAILURES=$((FAILURES + 1)); } -# Test 2: Creates deployment-summary.txt with CREATE transactions only +# Test 2: Logs CREATE entries to stdout ( TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT @@ -33,13 +33,12 @@ echo "=== Testing scripts/deploy/parse-broadcast.sh ===" export GITHUB_OUTPUT="$TMPDIR/github-output.txt" touch "$GITHUB_OUTPUT" - bash "$SCRIPT_DIR/scripts/deploy/parse-broadcast.sh" > /dev/null + OUTPUT=$(bash "$SCRIPT_DIR/scripts/deploy/parse-broadcast.sh") - [[ $(wc -l < deployment-summary.txt) -eq 2 ]] || { echo "FAIL: expected 2 lines, got $(wc -l < deployment-summary.txt)"; exit 1; } - grep -q "MyToken" deployment-summary.txt || { echo "FAIL: MyToken not found"; exit 1; } - grep -q "MyProxy" deployment-summary.txt || { echo "FAIL: MyProxy not found"; exit 1; } + echo "$OUTPUT" | grep -q "MyToken" || { echo "FAIL: MyToken not logged"; exit 1; } + echo "$OUTPUT" | grep -q "MyProxy" || { echo "FAIL: MyProxy not logged"; exit 1; } - echo "PASS: deployment-summary.txt has correct CREATE entries" + echo "PASS: logs CREATE entries" ) || { FAILURES=$((FAILURES + 1)); } # Test 3: Fails when no broadcast file exists