Skip to content
Merged
82 changes: 82 additions & 0 deletions .github/scripts/check-diff-changeset.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash
# Analyzes a diff between two directories and validates changeset requirements
# Usage: check-diff-changeset.sh <analysis-type> <base-dir> <head-dir>
# analysis-type: bytecode | storage
#
# For bytecode: any change requires patch+
# For storage: additions require minor+, removals require major

set -euo pipefail

ANALYSIS_TYPE=${1:-}
BASE_DIR=${2:-}
HEAD_DIR=${3:-}

if [ -z "$ANALYSIS_TYPE" ] || [ -z "$BASE_DIR" ] || [ -z "$HEAD_DIR" ]; then
echo "Usage: check-diff-changeset.sh <bytecode|storage> <base-dir> <head-dir>"
exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Generate diff
DIFF_OUTPUT=$(diff --unified --recursive "$BASE_DIR" "$HEAD_DIR" || true)

if [ -z "$DIFF_OUTPUT" ]; then
echo "No $ANALYSIS_TYPE changes detected."
exit 0
fi

echo "Detected $ANALYSIS_TYPE changes:"
echo "$DIFF_OUTPUT"
echo ""

# Classify changes
HAS_REMOVALS=false
HAS_ADDITIONS=false

if echo "$DIFF_OUTPUT" | grep -E '^-[^-]' >/dev/null; then
HAS_REMOVALS=true
fi
if echo "$DIFF_OUTPUT" | grep -E '^\+[^+]' >/dev/null; then
HAS_ADDITIONS=true
fi

# Determine required level based on analysis type and change classification
case "$ANALYSIS_TYPE" in
bytecode)
# Any bytecode change requires patch
REQUIRED_LEVEL="patch"
CHANGE_DESC="Bytecode changes"
;;
storage)
if [ "$HAS_REMOVALS" = true ]; then
REQUIRED_LEVEL="major"
CHANGE_DESC="Storage layout removals (breaking change)"
elif [ "$HAS_ADDITIONS" = true ]; then
REQUIRED_LEVEL="minor"
CHANGE_DESC="Storage layout additions"
else
echo "No significant storage changes detected."
exit 0
fi
;;
*)
echo "Unknown analysis type: $ANALYSIS_TYPE"
exit 1
;;
esac

echo "$CHANGE_DESC detected."
echo ""

# Check for adequate changeset
if "$SCRIPT_DIR/check-solidity-changeset.sh" "$REQUIRED_LEVEL"; then
echo ""
echo "$CHANGE_DESC are permitted with the existing changeset."
exit 0
else
echo ""
echo "ERROR: $CHANGE_DESC require a changeset for @hyperlane-xyz/core with at least a '$REQUIRED_LEVEL' bump."
exit 1
fi
61 changes: 61 additions & 0 deletions .github/scripts/check-solidity-changeset.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash
# Checks if @hyperlane-xyz/core has a changeset at or above the required level
# Usage: check-solidity-changeset.sh <required-level>
# Levels: patch < minor < major
# Exit 0 if adequate changeset exists, exit 1 otherwise

set -euo pipefail

REQUIRED_LEVEL=${1:-}
PACKAGE="@hyperlane-xyz/core"

if [ -z "$REQUIRED_LEVEL" ]; then
echo "Usage: check-solidity-changeset.sh <patch|minor|major>"
exit 1
fi

# Map levels to numeric values for comparison
level_to_num() {
case "$1" in
patch) echo 1 ;;
minor) echo 2 ;;
major) echo 3 ;;
*) echo 0 ;;
esac
}

REQUIRED_NUM=$(level_to_num "$REQUIRED_LEVEL")
FOUND_LEVEL=""
FOUND_NUM=0

# Scan all changeset files (excluding README.md and config.json)
for file in .changeset/*.md; do
[ -f "$file" ] || continue
[[ "$(basename "$file")" == "README.md" ]] && continue

# Extract the bump level for @hyperlane-xyz/core from YAML frontmatter
# Handles both quoted and unquoted package names
LEVEL=$(sed -n '/^---$/,/^---$/p' "$file" | grep -E "^['\"]?${PACKAGE}['\"]?:" | sed "s/.*: *//" | tr -d "'" | tr -d '"' || true)

if [ -n "$LEVEL" ]; then
LEVEL_NUM=$(level_to_num "$LEVEL")
if [ "$LEVEL_NUM" -gt "$FOUND_NUM" ]; then
FOUND_NUM=$LEVEL_NUM
FOUND_LEVEL=$LEVEL
fi
fi
done

if [ "$FOUND_NUM" -ge "$REQUIRED_NUM" ]; then
echo "Found $PACKAGE changeset with '$FOUND_LEVEL' bump (required: $REQUIRED_LEVEL or higher)"
exit 0
else
if [ -n "$FOUND_LEVEL" ]; then
echo "Found $PACKAGE changeset with '$FOUND_LEVEL' bump, but '$REQUIRED_LEVEL' or higher is required"
else
echo "No changeset found for $PACKAGE (required: $REQUIRED_LEVEL or higher)"
fi
echo ""
echo "To fix this, run 'pnpm changeset' and select '$PACKAGE' with a '$REQUIRED_LEVEL' (or higher) bump."
exit 1
fi
17 changes: 2 additions & 15 deletions .github/workflows/bytecode-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,5 @@ jobs:
run: pnpm -C solidity run bytecode base-bytecode

# Compare outputs
- name: Compare outputs (fail on any changes)
run: |
DIFF_OUTPUT=$(diff --unified --recursive solidity/base-bytecode solidity/HEAD-bytecode || true)

if [ -z "$DIFF_OUTPUT" ]; then
echo "No bytecode changes detected."
exit 0
fi

echo "Detected bytecode changes:"
echo "$DIFF_OUTPUT"
echo ""
echo "ERROR: Bytecode changes detected. This job fails if there are any bytecode changes."
echo "If these changes are expected, please review them carefully."
exit 1
- name: Compare outputs
run: .github/scripts/check-diff-changeset.sh bytecode solidity/base-bytecode solidity/HEAD-bytecode
39 changes: 36 additions & 3 deletions .github/workflows/interface-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,39 @@ jobs:
- name: Run command on target branch
run: pnpm -C solidity interface base-interface

# Compare outputs (only fail on function removals)
- name: Compare outputs (fail on function removals)
run: pnpm -C solidity interface test-interface base-interface HEAD-interface
# Compare outputs and check for appropriate changeset
- name: Compare outputs
run: |
set +e
pnpm -C solidity interface test-interface base-interface HEAD-interface
EXIT_CODE=$?
set -e

if [ "$EXIT_CODE" -eq 0 ]; then
echo "No interface changes detected."
exit 0
elif [ "$EXIT_CODE" -eq 2 ]; then
# Additions only - require minor changeset
echo ""
if .github/scripts/check-solidity-changeset.sh minor; then
echo ""
echo "Interface additions are permitted with the existing changeset."
exit 0
else
echo ""
echo "ERROR: Interface additions require a changeset for @hyperlane-xyz/core with at least a 'minor' bump."
exit 1
fi
else
# Removals detected - require major changeset
echo ""
if .github/scripts/check-solidity-changeset.sh major; then
echo ""
echo "Interface removals are permitted with the existing changeset."
exit 0
else
echo ""
echo "ERROR: Interface removals (breaking changes) require a changeset for @hyperlane-xyz/core with a 'major' bump."
exit 1
fi
fi
16 changes: 4 additions & 12 deletions .github/workflows/storage-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ on:
jobs:
diff-check:
runs-on: ubuntu-latest

# Skip on changeset version PRs, as storage layout is expected to change with package version bumps
if: github.head_ref != 'changeset-release/main'
steps:
# Checkout the PR branch
- name: Checkout PR branch
Expand Down Expand Up @@ -65,14 +66,5 @@ jobs:
run: pnpm -C solidity storage base-storage

# Compare outputs
- name: Compare outputs (fail on removals only)
run: |
DIFF_OUTPUT=$(diff --unified solidity/base-storage solidity/HEAD-storage || true)
echo "$DIFF_OUTPUT"
# Fail only if there are removal lines in diff hunks (lines starting with '-' but not '---')
if echo "$DIFF_OUTPUT" | grep -E '^-([^-])' >/dev/null; then
echo "Detected storage removals in diff. Failing job."
exit 1
else
echo "No storage removals detected."
fi
- name: Compare outputs
run: .github/scripts/check-diff-changeset.sh storage solidity/base-storage solidity/HEAD-storage
20 changes: 20 additions & 0 deletions solidity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ pnpm test

Some forge tests may generate fixtures. This allows the [SDK](https://github.com/hyperlane-xyz/hyperlane-monorepo/tree/main/typescript/sdk) tests to leverage forge fuzzing. These are git ignored and should not be committed.

## Contributing

When modifying Solidity contracts, CI checks will validate that appropriate changesets are included based on the type of change:

| Analysis | Change Type | Required Changeset |
| ------------- | ---------------------------------------- | ------------------ |
| **Bytecode** | Any change | `patch` or higher |
| **Interface** | Addition (new functions, events, errors) | `minor` or higher |
| **Interface** | Removal or modification | `major` |
| **Storage** | Addition (new storage slots) | `minor` or higher |
| **Storage** | Removal | `major` |

To add a changeset, run:

```bash
pnpm changeset
```

Select `@hyperlane-xyz/core` and choose the appropriate bump level based on your changes.

## License

Apache 2.0
Loading
Loading