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