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
1 change: 1 addition & 0 deletions .github/workflows/_deploy-testnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/_foundry-cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
62 changes: 51 additions & 11 deletions scripts/deploy/deployment-summary.sh
Original file line number Diff line number Diff line change
@@ -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"
Comment on lines 8 to 10
#
# 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 "<details>"
echo "<summary>Method Calls ($CALL_COUNT)</summary>"
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 "</details>"
fi
} >> "$GITHUB_STEP_SUMMARY"
4 changes: 2 additions & 2 deletions scripts/deploy/parse-broadcast.sh
Original file line number Diff line number Diff line change
@@ -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.
#
Comment on lines 3 to 5
# Outputs (via $GITHUB_OUTPUT):
# broadcast_file — path to the broadcast JSON
Expand All @@ -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"
12 changes: 9 additions & 3 deletions tests/fixtures/broadcast/Deploy.s.sol/31337/run-latest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
]
}
102 changes: 87 additions & 15 deletions tests/test-deployment-summary.sh
Original file line number Diff line number Diff line change
@@ -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 "<details>" "$GITHUB_STEP_SUMMARY" || { echo "FAIL: <details> 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
11 changes: 5 additions & 6 deletions tests/test-parse-broadcast.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading