|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Bytecode Comparison Tool (Metadata-Stripped) |
| 4 | +# |
| 5 | +# Compares functional bytecode between two contract artifact directories, |
| 6 | +# excluding metadata hashes to focus on actual code differences. |
| 7 | +# |
| 8 | +# This is an enhanced version of bytecode-diff.sh that strips Solidity |
| 9 | +# metadata hashes before comparison, allowing you to identify functional |
| 10 | +# differences vs compilation environment differences. |
| 11 | +# |
| 12 | +# Usage: ./bytecode-diff-no-metadata.sh <dir1> <dir2> |
| 13 | +# Example: ./bytecode-diff-no-metadata.sh /path/to/old/artifacts /path/to/new/artifacts |
| 14 | +# |
| 15 | +# Metadata Pattern Stripped: a264697066735822[64 hex chars]64736f6c63[6 hex chars] |
| 16 | +# This represents: "ipfs" + IPFS hash + "solc" + Solidity version |
| 17 | +# |
| 18 | + |
| 19 | +set -euo pipefail |
| 20 | + |
| 21 | +if [ "$#" -ne 2 ]; then |
| 22 | + echo "Usage: $0 <artifacts-dir-1> <artifacts-dir-2>" |
| 23 | + echo "This script compares bytecode excluding metadata hashes" |
| 24 | + echo "Metadata hashes are embedded by Solidity and don't affect contract functionality" |
| 25 | + exit 1 |
| 26 | +fi |
| 27 | + |
| 28 | +DIR1="$1" |
| 29 | +DIR2="$2" |
| 30 | + |
| 31 | +TMPDIR=$(mktemp -d) |
| 32 | + |
| 33 | +# Function to extract bytecode and strip metadata hash |
| 34 | +strip_metadata() { |
| 35 | + local file="$1" |
| 36 | + local out="$2" |
| 37 | + |
| 38 | + # Extract bytecode |
| 39 | + local bytecode=$(jq -r '.bytecode' "$file") |
| 40 | + |
| 41 | + # Remove 0x prefix if present |
| 42 | + bytecode=${bytecode#0x} |
| 43 | + |
| 44 | + # Strip metadata hash - Solidity metadata follows pattern: |
| 45 | + # a264697066735822<32-byte-hash>64736f6c63<version> |
| 46 | + # Where: |
| 47 | + # - a264697066735822 = "ipfs" in hex + length prefix |
| 48 | + # - 64736f6c63 = "solc" in hex |
| 49 | + # We'll remove everything from the last occurrence of a264697066735822 to the end |
| 50 | + |
| 51 | + # Use sed to remove the metadata pattern from the end |
| 52 | + # This removes everything from a264697066735822 (ipfs marker) to the end |
| 53 | + bytecode=$(echo "$bytecode" | sed 's/a264697066735822.*$//') |
| 54 | + |
| 55 | + # Output in chunks of 64 characters for easier diffing |
| 56 | + echo "$bytecode" | fold -w 64 > "$out" |
| 57 | +} |
| 58 | + |
| 59 | +echo "🔍 Comparing bytecode (excluding metadata hashes) for repository contracts..." |
| 60 | +echo "DIR1: $DIR1" |
| 61 | +echo "DIR2: $DIR2" |
| 62 | +echo |
| 63 | + |
| 64 | +# Create lists of contracts in each directory |
| 65 | +contracts1="$TMPDIR/contracts1.txt" |
| 66 | +contracts2="$TMPDIR/contracts2.txt" |
| 67 | + |
| 68 | +find "$DIR1/contracts" -type f -name '*.json' ! -name '*dbg.json' ! -name 'I*.json' 2>/dev/null | while read -r file; do |
| 69 | + rel_path="${file#$DIR1/contracts/}" |
| 70 | + echo "$rel_path" |
| 71 | +done | sort > "$contracts1" |
| 72 | + |
| 73 | +find "$DIR2/contracts" -type f -name '*.json' ! -name '*dbg.json' ! -name 'I*.json' 2>/dev/null | while read -r file; do |
| 74 | + rel_path="${file#$DIR2/contracts/}" |
| 75 | + echo "$rel_path" |
| 76 | +done | sort > "$contracts2" |
| 77 | + |
| 78 | +# Find common contracts |
| 79 | +common_contracts="$TMPDIR/common.txt" |
| 80 | +comm -12 "$contracts1" "$contracts2" > "$common_contracts" |
| 81 | + |
| 82 | +common_count=$(wc -l < "$common_contracts") |
| 83 | +echo "📊 Found $common_count common contracts to compare" |
| 84 | +echo |
| 85 | + |
| 86 | +if [ "$common_count" -eq 0 ]; then |
| 87 | + echo "❌ No common contracts found!" |
| 88 | + exit 1 |
| 89 | +fi |
| 90 | + |
| 91 | +# Compare bytecode for common contracts |
| 92 | +diff_count=0 |
| 93 | +same_count=0 |
| 94 | +no_bytecode_count=0 |
| 95 | + |
| 96 | +# Store results for summary |
| 97 | +same_contracts="$TMPDIR/same.txt" |
| 98 | +diff_contracts="$TMPDIR/different.txt" |
| 99 | +touch "$same_contracts" "$diff_contracts" |
| 100 | + |
| 101 | +echo "Processing contracts..." |
| 102 | + |
| 103 | +while read -r contract; do |
| 104 | + file1="$DIR1/contracts/$contract" |
| 105 | + file2="$DIR2/contracts/$contract" |
| 106 | + |
| 107 | + # Extract and strip metadata |
| 108 | + tmp1="$TMPDIR/1" |
| 109 | + tmp2="$TMPDIR/2" |
| 110 | + |
| 111 | + strip_metadata "$file1" "$tmp1" |
| 112 | + strip_metadata "$file2" "$tmp2" |
| 113 | + |
| 114 | + # Skip if no bytecode (interfaces, abstract contracts) |
| 115 | + if [ ! -s "$tmp1" ] || [ "$(wc -c < "$tmp1")" -le 3 ]; then |
| 116 | + no_bytecode_count=$((no_bytecode_count + 1)) |
| 117 | + continue |
| 118 | + fi |
| 119 | + |
| 120 | + contract_name=$(jq -r '.contractName // "Unknown"' "$file1" 2>/dev/null || echo "Unknown") |
| 121 | + |
| 122 | + if ! diff -q "$tmp1" "$tmp2" > /dev/null; then |
| 123 | + diff_count=$((diff_count + 1)) |
| 124 | + echo "$contract ($contract_name)" >> "$diff_contracts" |
| 125 | + echo "🧨 $contract" |
| 126 | + else |
| 127 | + same_count=$((same_count + 1)) |
| 128 | + echo "$contract ($contract_name)" >> "$same_contracts" |
| 129 | + echo "✅ $contract" |
| 130 | + fi |
| 131 | +done < "$common_contracts" |
| 132 | + |
| 133 | +echo |
| 134 | +echo "📋 SUMMARY LISTS:" |
| 135 | +echo |
| 136 | +echo "✅ FUNCTIONALLY IDENTICAL ($same_count contracts):" |
| 137 | +if [ -s "$same_contracts" ]; then |
| 138 | + cat "$same_contracts" | sed 's/^/ - /' |
| 139 | +else |
| 140 | + echo " (none)" |
| 141 | +fi |
| 142 | + |
| 143 | +echo |
| 144 | +echo "🧨 FUNCTIONAL DIFFERENCES ($diff_count contracts):" |
| 145 | +if [ -s "$diff_contracts" ]; then |
| 146 | + cat "$diff_contracts" | sed 's/^/ - /' |
| 147 | +else |
| 148 | + echo " (none)" |
| 149 | +fi |
| 150 | + |
| 151 | +echo |
| 152 | +echo "📊 Final Summary:" |
| 153 | +echo " Total contracts compared: $((same_count + diff_count))" |
| 154 | +echo " No bytecode (interfaces/abstract): $no_bytecode_count" |
| 155 | +echo " Functionally identical: $same_count" |
| 156 | +echo " Functional differences: $diff_count" |
| 157 | + |
| 158 | +if [ "$diff_count" -eq 0 ]; then |
| 159 | + echo |
| 160 | + echo "🎉 SUCCESS: All contracts are functionally identical!" |
| 161 | + echo " The previous differences were only in metadata hashes." |
| 162 | +else |
| 163 | + echo |
| 164 | + echo "⚠️ ATTENTION: Found $diff_count contracts with functional differences!" |
| 165 | + echo " These contracts have actual code changes that affect functionality." |
| 166 | +fi |
| 167 | + |
| 168 | +rm -rf "$TMPDIR" |
0 commit comments