Skip to content

Commit 41fd673

Browse files
committed
feat: storage-diff.sh
1 parent ecaff63 commit 41fd673

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

bin/storage-diff.sh

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/bin/bash
2+
3+
# Default values
4+
RPC_URL="https://eth.llamarpc.com"
5+
ETHERSCAN_API_KEY=""
6+
INPUT_FILE="solari.json"
7+
QUIET=false
8+
9+
# Help message
10+
usage() {
11+
cat << EOF
12+
Usage: bash $0 --rpc-url=$RPC_URL --etherscan-key=$ETHERSCAN_API_KEY [--input=$INPUT_FILE] [--quiet] [--help]
13+
14+
Detects storage layout incompatibilities that could cause issues during upgrades.
15+
16+
Required:
17+
-r, --rpc-url <url> RPC endpoint URL for the target network (e.g. https://eth.llamarpc.com)
18+
-e, --etherscan-key <key> API key for Etherscan to fetch contract data
19+
20+
Options:
21+
-i, --input <file> JSON file containing contract details (see format below)
22+
If not provided, reads from stdin
23+
-q, --quiet Suppress informational output
24+
-h, --help Show this help message
25+
26+
27+
Input JSON format:
28+
{
29+
"contracts": [
30+
{
31+
"name": "AVSDirectory",
32+
"address": "0x135dda560e946695d6f155dacafc6f1f25c1f5af"
33+
},
34+
{
35+
"name": "DelegationManager",
36+
"address": "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A"
37+
}
38+
]
39+
}
40+
EOF
41+
exit 1
42+
}
43+
44+
# Process command line arguments using a while loop and case statement.
45+
# This allows for both short (-r) and long (--rpc-url) argument formats.
46+
while [[ $# -gt 0 ]]; do
47+
case $1 in
48+
-r|--rpc-url)
49+
RPC_URL="$2"
50+
shift 2
51+
;;
52+
-e|--etherscan-key)
53+
ETHERSCAN_API_KEY="$2"
54+
shift 2
55+
;;
56+
-i|--input)
57+
INPUT_FILE="$2"
58+
shift 2
59+
;;
60+
-q|--quiet)
61+
QUIET=true
62+
shift
63+
;;
64+
-h|--help)
65+
usage
66+
;;
67+
*)
68+
echo "Unknown option: $1"
69+
usage
70+
;;
71+
esac
72+
done
73+
74+
# Validate required arguments
75+
if [ -z "$RPC_URL" ] || [ -z "$ETHERSCAN_API_KEY" ]; then
76+
echo "Error: RPC URL and Etherscan API key are required"
77+
usage
78+
fi
79+
80+
# Read JSON input
81+
if [ -n "$INPUT_FILE" ]; then
82+
if [ ! -f "$INPUT_FILE" ]; then
83+
echo "Error: Input file not found: $INPUT_FILE"
84+
exit 1
85+
fi
86+
json_input=$(cat "$INPUT_FILE")
87+
else
88+
json_input=$(cat)
89+
fi
90+
91+
# Parse JSON values using jq
92+
CONTRACTS=$(echo "$json_input" | jq -c '.contracts[]')
93+
94+
# Verify contracts are specified
95+
if [ -z "$CONTRACTS" ]; then
96+
echo "Error: No contracts specified in JSON input"
97+
exit 1
98+
fi
99+
100+
# Function to process a single contract
101+
process_contract() {
102+
local contract_name=$1
103+
local contract_address=$2
104+
105+
# Temporary files
106+
local local_file=$(mktemp)
107+
local onchain_file=$(mktemp)
108+
109+
# Generate storage layouts
110+
if ! forge inspect "$contract_name" storage --json > "$local_file" 2>/dev/null; then
111+
echo "Error: forge inspect failed for contract: $contract_name"
112+
rm "$local_file" "$onchain_file"
113+
return 1
114+
fi
115+
116+
if ! cast storage "$contract_address" --rpc-url "$RPC_URL" --etherscan-api-key "$ETHERSCAN_API_KEY" --json > "$onchain_file" 2>/dev/null; then
117+
echo "Error: cast storage failed for address: $contract_address"
118+
rm "$local_file" "$onchain_file"
119+
return 1
120+
fi
121+
122+
# Delete the first line of $onchain_file
123+
sed '1d' "$onchain_file" > "$onchain_file.tmp" && mv "$onchain_file.tmp" "$onchain_file"
124+
125+
# Filter out astId and contract fields from local and onchain files
126+
jq 'del(.types, .values, .storage[].astId, .storage[].contract, .storage[].type)' "$local_file" > "${local_file}.tmp" && mv "${local_file}.tmp" "$local_file"
127+
jq 'del(.types, .values, .storage[].astId, .storage[].contract, .storage[].type)' "$onchain_file" > "${onchain_file}.tmp" && mv "${onchain_file}.tmp" "$onchain_file"
128+
129+
# Compare storage layouts and provide feedback
130+
if ! diff "$onchain_file" "$local_file" > "${contract_name}.diff"; then
131+
echo "Storage Layout Differences Detected for $contract_name!"
132+
if [ "$QUIET" = false ]; then
133+
echo "----------------------------------------"
134+
echo "Local contract: $contract_name"
135+
echo "Chain address: $contract_address"
136+
echo "Network RPC: $RPC_URL"
137+
echo "----------------------------------------"
138+
# Format diff output with colors
139+
while IFS= read -r line; do
140+
if [[ $line == \<* ]]; then
141+
echo -e "\033[31m$line\033[0m" # Red for removed lines
142+
elif [[ $line == \>* ]]; then
143+
echo -e "\033[32m$line\033[0m" # Green for added lines
144+
elif [[ $line == ---* ]] || [[ $line == [0-9]* ]]; then
145+
echo -e "\033[36m$line\033[0m" # Cyan for metadata lines
146+
else
147+
echo " $line"
148+
fi
149+
done < "${contract_name}.diff"
150+
fi
151+
else
152+
echo "Storage layouts match for $contract_name!"
153+
rm -f "${contract_name}.diff"
154+
fi
155+
156+
# Cleanup
157+
rm -f "$local_file" "$onchain_file"
158+
}
159+
160+
# Process each contract from the JSON input
161+
while IFS= read -r contract; do
162+
contract_name=$(echo "$contract" | jq -r '.name')
163+
contract_address=$(echo "$contract" | jq -r '.address')
164+
165+
if [ -z "$contract_name" ] || [ -z "$contract_address" ]; then
166+
echo "Error: Each contract must specify both name and address"
167+
continue
168+
fi
169+
170+
if [ "$QUIET" = false ]; then
171+
echo "Processing contract: $contract_name at $contract_address"
172+
fi
173+
process_contract "$contract_name" "$contract_address"
174+
done <<< "$CONTRACTS"

0 commit comments

Comments
 (0)