2026-02-05 11:08:35 +00:00
|
|
|
#!/bin/bash
|
|
|
|
|
set -e
|
|
|
|
|
|
|
|
|
|
# Storage Layout Check Script
|
|
|
|
|
# Compares current storage layout against committed snapshot to detect unintended changes.
|
|
|
|
|
|
|
|
|
|
CONTRACT="${CONTRACT:-DataHavenServiceManager}"
|
|
|
|
|
SNAPSHOT_DIR="${SNAPSHOT_DIR:-storage-snapshots}"
|
|
|
|
|
SNAPSHOT="${SNAPSHOT:-${SNAPSHOT_DIR}/${CONTRACT}.storage.json}"
|
|
|
|
|
|
|
|
|
|
# Ensure we're in the contracts directory
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
cd "$SCRIPT_DIR/.."
|
|
|
|
|
|
|
|
|
|
# Check if snapshot exists
|
|
|
|
|
if [ ! -f "$SNAPSHOT" ]; then
|
|
|
|
|
echo "ERROR: Snapshot file not found: $SNAPSHOT"
|
|
|
|
|
echo "Generate it with: mkdir -p $SNAPSHOT_DIR && forge inspect $CONTRACT storage --json > $SNAPSHOT"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Generate current layout
|
|
|
|
|
echo "Generating current storage layout for $CONTRACT..."
|
|
|
|
|
forge inspect "$CONTRACT" storage --json > /tmp/current_layout.json
|
|
|
|
|
|
|
|
|
|
# Normalize both files for comparison:
|
|
|
|
|
# - Remove astId (changes with compiler runs)
|
|
|
|
|
# - Remove contract field (contains full path)
|
|
|
|
|
# - Remove types section (contains unstable AST IDs)
|
|
|
|
|
# - Sort by slot number
|
|
|
|
|
normalize_json() {
|
|
|
|
|
jq 'del(.types)
|
|
|
|
|
| .storage
|
|
|
|
|
| map(
|
|
|
|
|
del(.astId, .contract)
|
2026-02-24 08:23:57 +00:00
|
|
|
# Remove unstable AST IDs from type strings (e.g., t_contract(IGatewayV2)12345, nested mappings)
|
|
|
|
|
| .type |= gsub("\\)[0-9]+"; ")")
|
2026-02-05 11:08:35 +00:00
|
|
|
)
|
|
|
|
|
| sort_by(.slot | tonumber)' "$1"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "Comparing storage layouts..."
|
|
|
|
|
normalize_json "$SNAPSHOT" > /tmp/snap_normalized.json
|
|
|
|
|
normalize_json /tmp/current_layout.json > /tmp/curr_normalized.json
|
|
|
|
|
|
|
|
|
|
if ! diff -q /tmp/snap_normalized.json /tmp/curr_normalized.json > /dev/null 2>&1; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=========================================="
|
|
|
|
|
echo "ERROR: Storage layout has changed!"
|
|
|
|
|
echo "=========================================="
|
|
|
|
|
echo ""
|
|
|
|
|
echo "Differences found:"
|
|
|
|
|
diff /tmp/snap_normalized.json /tmp/curr_normalized.json || true
|
|
|
|
|
echo ""
|
|
|
|
|
echo "If this change is intentional, update the snapshot:"
|
|
|
|
|
echo " forge inspect $CONTRACT storage --json > $SNAPSHOT"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "WARNING: Unintended storage layout changes can corrupt state during upgrades!"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-20 08:29:34 +00:00
|
|
|
# Verify gap invariant: __GAP slot + array size must equal a fixed constant.
|
|
|
|
|
# This catches cases where a new variable is added but __GAP is not shrunk accordingly.
|
|
|
|
|
EXPECTED_GAP_TOTAL=151
|
|
|
|
|
GAP_SLOT=$(jq '.storage[] | select(.label == "__GAP") | .slot | tonumber' /tmp/current_layout.json)
|
|
|
|
|
GAP_SIZE=$(jq -r '.storage[] | select(.label == "__GAP") | .type' /tmp/current_layout.json \
|
|
|
|
|
| grep -oE '[0-9]+' | tail -1)
|
|
|
|
|
|
|
|
|
|
if [ -n "$GAP_SLOT" ] && [ -n "$GAP_SIZE" ]; then
|
|
|
|
|
GAP_TOTAL=$((GAP_SLOT + GAP_SIZE))
|
|
|
|
|
if [ "$GAP_TOTAL" -ne "$EXPECTED_GAP_TOTAL" ]; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo "=========================================="
|
|
|
|
|
echo "ERROR: __GAP invariant violated!"
|
|
|
|
|
echo "=========================================="
|
|
|
|
|
echo ""
|
|
|
|
|
echo " slot($GAP_SLOT) + size($GAP_SIZE) = $GAP_TOTAL, expected $EXPECTED_GAP_TOTAL"
|
|
|
|
|
echo ""
|
|
|
|
|
echo "If you added a new state variable, shrink __GAP by the same number of slots."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-05 11:08:35 +00:00
|
|
|
echo "Storage layout OK - no changes detected"
|