Skip to content

Commit 47b7aa3

Browse files
committed
include storage slot diff in the trace call test script
1 parent f36d8c3 commit 47b7aa3

1 file changed

Lines changed: 122 additions & 53 deletions

File tree

scripts/tests/trace_call_integration_test.sh

Lines changed: 122 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
#!/usr/bin/env bash
2-
# Trace Call Comparison Test - Compares Forest's trace_call with Anvil's debug_traceCall
3-
# Usage: ./trace_call_integration_test.sh [--deploy] [--verbose]
42
set -e
53

6-
# --- Parse Flags ---
74
DEPLOY_CONTRACT=false
85
VERBOSE=false
96
while [[ $# -gt 0 ]]; do
@@ -17,29 +14,25 @@ done
1714
# --- Configuration ---
1815
FOREST_RPC_URL="${FOREST_RPC_URL:-http://localhost:2345/rpc/v1}"
1916
ANVIL_RPC_URL="${ANVIL_RPC_URL:-http://localhost:8545}"
20-
FOREST_ACCOUNT="${FOREST_ACCOUNT:- "0xb7aa1e9c847cda5f60f1ae6f65c3eae44848d41f"}"
21-
FOREST_CONTRACT="${FOREST_CONTRACT:- "0x8724d2eb7f86ebaef34e050b02fac6c268e56775"}"
22-
ANVIL_ACCOUNT="${ANVIL_ACCOUNT:-"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}"
23-
ANVIL_CONTRACT="${ANVIL_CONTRACT:-"0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"}"
24-
ANVIL_PRIVATE_KEY="${ANVIL_PRIVATE_KEY:- ""}"
17+
FOREST_ACCOUNT="${FOREST_ACCOUNT:-0xb7aa1e9c847cda5f60f1ae6f65c3eae44848d41f}"
18+
FOREST_CONTRACT="${FOREST_CONTRACT:-0x73a43475aa2ccb14246613708b399f4b2ba546c7}"
19+
ANVIL_ACCOUNT="${ANVIL_ACCOUNT:-0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266}"
20+
ANVIL_CONTRACT="${ANVIL_CONTRACT:-0x5FbDB2315678afecb367f032d93F642f64180aa3}"
21+
ANVIL_PRIVATE_KEY="${ANVIL_PRIVATE_KEY:-0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80}"
2522

2623
GREEN='\033[0;32m' RED='\033[0;31m' BLUE='\033[0;34m' YELLOW='\033[0;33m' NC='\033[0m'
2724
PASS_COUNT=0 FAIL_COUNT=0
2825

29-
# --- Dependency Check ---
3026
command -v jq &>/dev/null || { echo "Error: jq is required"; exit 1; }
3127
command -v curl &>/dev/null || { echo "Error: curl is required"; exit 1; }
3228

33-
# --- Unified RPC Dispatcher ---
34-
# Single entry point for all RPC calls - removes JSON-RPC boilerplate from test logic
3529
call_rpc() {
3630
local url="$1" method="$2" params="$3"
3731
curl -s -X POST "$url" \
3832
-H "Content-Type: application/json" \
3933
-d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$method\",\"params\":$params}"
4034
}
4135

42-
# --- RPC Health Check ---
4336
check_rpc() {
4437
local name="$1" url="$2"
4538
local resp=$(call_rpc "$url" "eth_chainId" "[]")
@@ -53,7 +46,6 @@ check_rpc() {
5346
check_rpc "Forest" "$FOREST_RPC_URL" || exit 1
5447
check_rpc "Anvil" "$ANVIL_RPC_URL" || exit 1
5548

56-
# --- Deploy Contract (if requested) ---
5749
if [ "$DEPLOY_CONTRACT" = true ]; then
5850
command -v forge &>/dev/null || { echo "Error: forge is required for --deploy"; exit 1; }
5951
echo -e "${YELLOW}Deploying Tracer contract on Anvil...${NC}"
@@ -65,26 +57,16 @@ if [ "$DEPLOY_CONTRACT" = true ]; then
6557
echo -e "Deployed to: ${GREEN}$ANVIL_CONTRACT${NC}"
6658
fi
6759

68-
# --- Normalization Helpers ---
69-
# Convert different node outputs into a standard format for comparison
70-
71-
# Normalize empty values: null, "", "0x" -> "0x"
7260
normalize_empty() {
7361
local val="$1"
7462
[[ "$val" == "null" || -z "$val" ]] && echo "0x" || echo "$val"
7563
}
7664

77-
# Get balance change type from Forest's Parity Delta format
78-
# Returns: "unchanged", "changed", "added", or "removed"
79-
get_balance_type() {
65+
get_delta_type() {
8066
local val="$1"
81-
# Handle unchanged cases
8267
if [[ "$val" == "=" || "$val" == "\"=\"" || "$val" == "null" || -z "$val" ]]; then
8368
echo "unchanged"
84-
return
85-
fi
86-
# Check for Delta types
87-
if echo "$val" | jq -e 'has("*")' &>/dev/null; then
69+
elif echo "$val" | jq -e 'has("*")' &>/dev/null; then
8870
echo "changed"
8971
elif echo "$val" | jq -e 'has("+")' &>/dev/null; then
9072
echo "added"
@@ -97,8 +79,6 @@ get_balance_type() {
9779

9880
assert_eq() {
9981
local label="$1" f_val="$2" a_val="$3"
100-
101-
# Normalize: lowercase and treat null/0x/empty as equivalent
10282
local f_norm=$(echo "$f_val" | tr '[:upper:]' '[:lower:]')
10383
local a_norm=$(echo "$a_val" | tr '[:upper:]' '[:lower:]')
10484
[[ "$f_norm" == "null" || -z "$f_norm" ]] && f_norm="0x"
@@ -116,36 +96,30 @@ assert_eq() {
11696
assert_both_have_error() {
11797
local f_err="$1" a_err="$2"
11898
if [[ -n "$f_err" && "$f_err" != "null" ]] && [[ -n "$a_err" && "$a_err" != "null" ]]; then
119-
echo -e " ${GREEN}[PASS]${NC} Error: both have error"
99+
echo -e " ${GREEN}[PASS]${NC} Both have error"
120100
PASS_COUNT=$((PASS_COUNT + 1))
121101
else
122-
echo -e " ${RED}[FAIL]${NC} Error: (Forest: '$f_err' | Anvil: '$a_err')"
102+
echo -e " ${RED}[FAIL]${NC} Error mismatch (Forest: '$f_err' | Anvil: '$a_err')"
123103
FAIL_COUNT=$((FAIL_COUNT + 1))
124104
fi
125105
}
126106

127-
# Compares Forest's trace_call [trace] with Anvil's debug_traceCall (callTracer)
128-
# Types: "standard" (default), "revert", "deep"
129107
test_trace() {
130108
local name="$1" data="$2" type="${3:-standard}"
131109
echo -e "${BLUE}--- $name ---${NC}"
132110

133-
# Forest: trace_call with trace
134111
local f_params="[{\"from\":\"$FOREST_ACCOUNT\",\"to\":\"$FOREST_CONTRACT\",\"data\":\"$data\"},[\"trace\"],\"latest\"]"
135112
local f_resp=$(call_rpc "$FOREST_RPC_URL" "trace_call" "$f_params")
136113

137-
# Anvil: debug_traceCall with callTracer
138114
local a_params="[{\"from\":\"$ANVIL_ACCOUNT\",\"to\":\"$ANVIL_CONTRACT\",\"data\":\"$data\"},\"latest\",{\"tracer\":\"callTracer\"}]"
139115
local a_resp=$(call_rpc "$ANVIL_RPC_URL" "debug_traceCall" "$a_params")
140116

141117
[[ "$VERBOSE" = true ]] && echo -e "${YELLOW}Forest:${NC} $f_resp\n${YELLOW}Anvil:${NC} $a_resp"
142118

143-
# Extract & compare input (common to all types)
144119
local f_input=$(echo "$f_resp" | jq -r '.result.trace[0].action.input')
145120
local a_input=$(echo "$a_resp" | jq -r '.result.input')
146121
assert_eq "Input" "$f_input" "$a_input"
147122

148-
# Type-specific comparisons
149123
case $type in
150124
revert)
151125
local f_err=$(echo "$f_resp" | jq -r '.result.trace[0].error // empty')
@@ -169,42 +143,114 @@ test_trace() {
169143
echo ""
170144
}
171145

172-
# Compares Forest's trace_call [stateDiff] with Anvil's prestateTracer (diffMode)
173-
test_state_diff() {
146+
test_balance_diff() {
174147
local name="$1" data="$2" value="${3:-0x0}" expect="${4:-unchanged}"
175-
echo -e "${BLUE}--- $name (stateDiff) ---${NC}"
148+
echo -e "${BLUE}--- $name (balance) ---${NC}"
176149

177-
# Forest: trace_call with stateDiff
178150
local f_params="[{\"from\":\"$FOREST_ACCOUNT\",\"to\":\"$FOREST_CONTRACT\",\"data\":\"$data\",\"value\":\"$value\"},[\"stateDiff\"],\"latest\"]"
179151
local f_resp=$(call_rpc "$FOREST_RPC_URL" "trace_call" "$f_params")
180152

181-
# Anvil: prestateTracer with diffMode
182153
local a_params="[{\"from\":\"$ANVIL_ACCOUNT\",\"to\":\"$ANVIL_CONTRACT\",\"data\":\"$data\",\"value\":\"$value\"},\"latest\",{\"tracer\":\"prestateTracer\",\"tracerConfig\":{\"diffMode\":true}}]"
183154
local a_resp=$(call_rpc "$ANVIL_RPC_URL" "debug_traceCall" "$a_params")
184155

185156
[[ "$VERBOSE" = true ]] && echo -e "${YELLOW}Forest:${NC} $f_resp\n${YELLOW}Anvil:${NC} $a_resp"
186157

187-
# Extract contract addresses (lowercase for jq lookup)
188158
local f_contract_lower=$(echo "$FOREST_CONTRACT" | tr '[:upper:]' '[:lower:]')
189159
local a_contract_lower=$(echo "$ANVIL_CONTRACT" | tr '[:upper:]' '[:lower:]')
190160

191-
# Extract Forest stateDiff balance
192-
local f_diff=$(echo "$f_resp" | jq '.result.stateDiff // {}')
193-
local f_bal=$(echo "$f_diff" | jq -r --arg a "$f_contract_lower" '.[$a].balance // "="')
194-
local f_type=$(get_balance_type "$f_bal")
161+
local f_bal=$(echo "$f_resp" | jq -r --arg a "$f_contract_lower" '.result.stateDiff[$a].balance // "="')
162+
local f_type=$(get_delta_type "$f_bal")
195163

196-
# Extract Anvil pre/post balance and determine change type
197164
local a_pre_bal=$(echo "$a_resp" | jq -r --arg a "$a_contract_lower" '.result.pre[$a].balance // "0x0"')
198165
local a_post_bal=$(echo "$a_resp" | jq -r --arg a "$a_contract_lower" '.result.post[$a].balance // "0x0"')
199166
local a_type="unchanged"
200167
[[ "$a_pre_bal" != "$a_post_bal" ]] && a_type="changed"
201168

202-
# Semantic assertions - compare intent, not raw values
203-
assert_eq "Forest matches Expected" "$f_type" "$expect"
204-
assert_eq "Forest matches Anvil" "$f_type" "$a_type"
169+
assert_eq "BalanceChange" "$f_type" "$expect"
170+
assert_eq "ForestMatchesAnvil" "$f_type" "$a_type"
205171
echo ""
206172
}
207173

174+
test_storage_diff() {
175+
local name="$1" data="$2" slot="$3" expect_type="${4:-changed}"
176+
echo -e "${BLUE}--- $name (storage) ---${NC}"
177+
178+
local f_params="[{\"from\":\"$FOREST_ACCOUNT\",\"to\":\"$FOREST_CONTRACT\",\"data\":\"$data\"},[\"stateDiff\"],\"latest\"]"
179+
local f_resp=$(call_rpc "$FOREST_RPC_URL" "trace_call" "$f_params")
180+
181+
local a_params="[{\"from\":\"$ANVIL_ACCOUNT\",\"to\":\"$ANVIL_CONTRACT\",\"data\":\"$data\"},\"latest\",{\"tracer\":\"prestateTracer\",\"tracerConfig\":{\"diffMode\":true}}]"
182+
local a_resp=$(call_rpc "$ANVIL_RPC_URL" "debug_traceCall" "$a_params")
183+
184+
[[ "$VERBOSE" = true ]] && echo -e "${YELLOW}Forest:${NC} $f_resp\n${YELLOW}Anvil:${NC} $a_resp"
185+
186+
local f_contract_lower=$(echo "$FOREST_CONTRACT" | tr '[:upper:]' '[:lower:]')
187+
local a_contract_lower=$(echo "$ANVIL_CONTRACT" | tr '[:upper:]' '[:lower:]')
188+
189+
# Forest: Extract storage slot delta and values
190+
local f_slot_data=$(echo "$f_resp" | jq -r --arg a "$f_contract_lower" --arg s "$slot" '.result.stateDiff[$a].storage[$s] // null')
191+
local f_type=$(get_delta_type "$f_slot_data")
192+
local f_to_val=""
193+
if [ "$f_type" = "changed" ]; then
194+
f_to_val=$(echo "$f_slot_data" | jq -r '.["*"].to // empty')
195+
elif [ "$f_type" = "added" ]; then
196+
f_to_val=$(echo "$f_slot_data" | jq -r '.["+"] // empty')
197+
fi
198+
199+
# Anvil: Extract storage slot pre/post values
200+
local a_pre_val=$(echo "$a_resp" | jq -r --arg a "$a_contract_lower" --arg s "$slot" '.result.pre[$a].storage[$s] // "0x0"')
201+
local a_post_val=$(echo "$a_resp" | jq -r --arg a "$a_contract_lower" --arg s "$slot" '.result.post[$a].storage[$s] // "0x0"')
202+
local a_type="unchanged"
203+
if [[ "$a_pre_val" == "0x0" || "$a_pre_val" == "null" ]] && [[ "$a_post_val" != "0x0" && "$a_post_val" != "null" ]]; then
204+
a_type="changed" # Added (was zero, now non-zero)
205+
elif [[ "$a_pre_val" != "0x0" && "$a_pre_val" != "null" ]] && [[ "$a_post_val" != "$a_pre_val" ]]; then
206+
a_type="changed" # Modified
207+
fi
208+
209+
assert_eq "StorageChangeType" "$f_type" "$expect_type"
210+
assert_eq "ForestMatchesAnvil" "$f_type" "$a_type"
211+
212+
# Compare actual values if both have the slot
213+
if [[ -n "$f_to_val" && -n "$a_post_val" && "$a_post_val" != "null" ]]; then
214+
assert_eq "StorageValue" "$f_to_val" "$a_post_val"
215+
fi
216+
echo ""
217+
}
218+
219+
test_storage_multiple() {
220+
local name="$1" data="$2"
221+
shift 2
222+
local slots=("$@")
223+
echo -e "${BLUE}--- $name (multi-storage) ---${NC}"
224+
225+
local f_params="[{\"from\":\"$FOREST_ACCOUNT\",\"to\":\"$FOREST_CONTRACT\",\"data\":\"$data\"},[\"stateDiff\"],\"latest\"]"
226+
local f_resp=$(call_rpc "$FOREST_RPC_URL" "trace_call" "$f_params")
227+
228+
local a_params="[{\"from\":\"$ANVIL_ACCOUNT\",\"to\":\"$ANVIL_CONTRACT\",\"data\":\"$data\"},\"latest\",{\"tracer\":\"prestateTracer\",\"tracerConfig\":{\"diffMode\":true}}]"
229+
local a_resp=$(call_rpc "$ANVIL_RPC_URL" "debug_traceCall" "$a_params")
230+
231+
[[ "$VERBOSE" = true ]] && echo -e "${YELLOW}Forest:${NC} $f_resp\n${YELLOW}Anvil:${NC} $a_resp"
232+
233+
local f_contract_lower=$(echo "$FOREST_CONTRACT" | tr '[:upper:]' '[:lower:]')
234+
local a_contract_lower=$(echo "$ANVIL_CONTRACT" | tr '[:upper:]' '[:lower:]')
235+
236+
local f_slot_count=$(echo "$f_resp" | jq -r --arg a "$f_contract_lower" '.result.stateDiff[$a].storage | length')
237+
local a_slot_count=$(echo "$a_resp" | jq -r --arg a "$a_contract_lower" '.result.post[$a].storage | length')
238+
assert_eq "SlotCount" "$f_slot_count" "$a_slot_count"
239+
240+
for slot in "${slots[@]}"; do
241+
local f_slot_data=$(echo "$f_resp" | jq -r --arg a "$f_contract_lower" --arg s "$slot" '.result.stateDiff[$a].storage[$s] // null')
242+
local f_to_val=$(echo "$f_slot_data" | jq -r '.["*"].to // .["+"] // empty')
243+
local a_post_val=$(echo "$a_resp" | jq -r --arg a "$a_contract_lower" --arg s "$slot" '.result.post[$a].storage[$s] // empty')
244+
245+
local slot_short="${slot: -4}"
246+
assert_eq "Slot$slot_short" "$f_to_val" "$a_post_val"
247+
done
248+
echo ""
249+
}
250+
251+
# =============================================================================
252+
# Main Execution
253+
# =============================================================================
208254
echo "=============================================="
209255
echo "Trace Call Comparison: Forest vs Anvil"
210256
echo "=============================================="
@@ -233,20 +279,43 @@ test_trace "deepTrace(3)" \
233279
"0x0f3a17b80000000000000000000000000000000000000000000000000000000000000003" \
234280
"deep"
235281

236-
# --- StateDiff Tests ---
237-
echo -e "${BLUE}=== StateDiff Tests ===${NC}"
282+
# --- Balance Diff Tests ---
283+
echo -e "${BLUE}=== Balance Diff Tests ===${NC}"
238284
echo ""
239285

240-
test_state_diff "deposit() with 1 ETH" \
286+
test_balance_diff "deposit() with 1 ETH" \
241287
"0xd0e30db0" \
242288
"0xde0b6b3a7640000" \
243289
"changed"
244290

245-
test_state_diff "setX(42) no value" \
291+
test_balance_diff "setX(42) no value" \
246292
"0x4018d9aa000000000000000000000000000000000000000000000000000000000000002a" \
247293
"0x0" \
248294
"unchanged"
249295

296+
# --- Storage Diff Tests ---
297+
echo -e "${BLUE}=== Storage Diff Tests ===${NC}"
298+
echo ""
299+
300+
# Slot 0: x variable (initialized to 42)
301+
test_storage_diff "setX(123) - change slot 0" \
302+
"0x4018d9aa000000000000000000000000000000000000000000000000000000000000007b" \
303+
"0x0000000000000000000000000000000000000000000000000000000000000000" \
304+
"changed"
305+
306+
# Slot 2: storageTestA (starts empty)
307+
test_storage_diff "storageAdd(100) - add slot 2" \
308+
"0x55cb64b40000000000000000000000000000000000000000000000000000000000000064" \
309+
"0x0000000000000000000000000000000000000000000000000000000000000002" \
310+
"changed"
311+
312+
# Multiple slots: storageTestA(10), storageTestB(20), storageTestC(30)
313+
test_storage_multiple "storageMultiple(10,20,30) - slots 2,3,4" \
314+
"0x310af204000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001e" \
315+
"0x0000000000000000000000000000000000000000000000000000000000000002" \
316+
"0x0000000000000000000000000000000000000000000000000000000000000003" \
317+
"0x0000000000000000000000000000000000000000000000000000000000000004"
318+
250319
# --- Results ---
251320
echo "=============================================="
252321
echo -e "Results: ${GREEN}Passed: $PASS_COUNT${NC} | ${RED}Failed: $FAIL_COUNT${NC}"

0 commit comments

Comments
 (0)