scripts/sepolia/fork.sh84 lines · 4573 bytes
| 1 | #!/usr/bin/env bash |
| 2 | # A local anvil chain with Sepolia's state, for rehearsals and for the "network is down" fallback. |
| 3 | # |
| 4 | # FORK=19631 scripts/sepolia/fork.sh start # anvil forking live Sepolia on 127.0.0.1:19631, 12 s blocks |
| 5 | # FORK=19631 scripts/sepolia/fork.sh save [dir] # snapshot the chain + deployment (default $TMPDIR/.../snapshot) |
| 6 | # FORK=19632 scripts/sepolia/fork.sh load <dir> # start an OFFLINE anvil from a snapshot: no upstream RPC needed |
| 7 | # FORK=19631 scripts/sepolia/fork.sh status |
| 8 | # FORK=19631 scripts/sepolia/fork.sh stop # kills the anvil this script started (by its PID file) |
| 9 | # |
| 10 | # Env: BLOCK_TIME (default 12; 0 = mine on every transaction), FORK_SOURCE (default publicnode), |
| 11 | # FORK_BLOCK (default: latest), READ_ONLY_ACCOUNTS (save: more contracts to carry into the snapshot although |
| 12 | # the chain never changed them; Multicall3 is always carried). |
| 13 | # Every other script in this folder takes the same FORK=<port> and then talks only to this local chain. |
| 14 | source "$(dirname "$0")/lib.sh" |
| 15 | [ "$MODE" = fork ] || die "set FORK=<port>" |
| 16 | PIDF=$STATE/anvil.pid |
| 17 | SOURCE=${FORK_SOURCE:-https://ethereum-sepolia-rpc.publicnode.com} |
| 18 | BT=${BLOCK_TIME:-12} |
| 19 | |
| 20 | running() { [ -f "$PIDF" ] && kill -0 "$(cat "$PIDF")" 2>/dev/null && ps -p "$(cat "$PIDF")" -o comm= | grep -q anvil; } |
| 21 | |
| 22 | launch() { # anvil args... |
| 23 | if running; then die "a chain is already running on :$FORK (pid $(cat "$PIDF")); stop it first"; fi |
| 24 | if cast chain-id --rpc-url "$RPC" >/dev/null 2>&1; then die "port $FORK is already in use by another node"; fi |
| 25 | local args=(--port "$FORK" --chain-id "$CHAIN_ID" "$@") |
| 26 | [ "$BT" != 0 ] && args+=(--block-time "$BT") |
| 27 | nohup anvil "${args[@]}" >"$STATE/anvil.log" 2>&1 & |
| 28 | echo $! >"$PIDF" |
| 29 | for _ in $(seq 1 60); do cast chain-id --rpc-url "$RPC" >/dev/null 2>&1 && break; sleep 0.5; done |
| 30 | check_rpc |
| 31 | } |
| 32 | |
| 33 | case "${1:-status}" in |
| 34 | start) |
| 35 | running && { say "fork already running on :$FORK (pid $(cat "$PIDF"))"; exit 0; } |
| 36 | rm -rf "$STATE" && mkdir -p "$STATE" # a new fork is a new chain: drop the old deployment and logs |
| 37 | extra=(--fork-url "$SOURCE") |
| 38 | [ -n "${FORK_BLOCK:-}" ] && extra+=(--fork-block-number "$FORK_BLOCK") |
| 39 | launch "${extra[@]}" |
| 40 | say "fork of Sepolia on $RPC (pid $(cat "$PIDF"), forked at block $(block_now), block time ${BT}s)" |
| 41 | say "state dir $STATE" |
| 42 | ;; |
| 43 | save) |
| 44 | running || die "no chain running on :$FORK" |
| 45 | OUT=${2:-$STATE/snapshot} |
| 46 | mkdir -p "$OUT" |
| 47 | # anvil_dumpState keeps only the accounts this chain changed. Contracts the workflow only reads (Multicall3 for |
| 48 | # its batched reads) would be missing from an offline replica, so set their code again to make them part of it. |
| 49 | MC=$(jq -r '.multicall // empty' "$STATE/config.fork.json" 2>/dev/null || true) |
| 50 | for a in "${MC:-0xcA11bde05977b3631167028862bE2a173976CA11}" ${READ_ONLY_ACCOUNTS:-}; do |
| 51 | code=$(cast code "$a" --rpc-url "$RPC") |
| 52 | [ "$code" = 0x ] || cast rpc anvil_setCode "$a" "$code" --rpc-url "$RPC" >/dev/null |
| 53 | done |
| 54 | # anvil_dumpState: hex of the gzipped JSON that `anvil --load-state` reads |
| 55 | cast rpc anvil_dumpState --rpc-url "$RPC" | tr -d '"' | sed 's/^0x//' | xxd -r -p | gunzip -c >"$OUT/state.json" |
| 56 | [ -f "$DEPLOY_JSON" ] && cp "$DEPLOY_JSON" "$OUT/deployment.json" |
| 57 | say "saved block $(block_now) of :$FORK to $OUT ($(du -h "$OUT/state.json" | cut -f1) state$([ -f "$OUT/deployment.json" ] && echo ' + deployment.json'))" |
| 58 | ;; |
| 59 | load) |
| 60 | SNAP=${2:-} |
| 61 | [ -f "$SNAP/state.json" ] || die "usage: FORK=<port> $0 load <snapshot dir with state.json>" |
| 62 | SNAP=$(cd "$SNAP" && pwd) |
| 63 | running && die "a chain is already running on :$FORK" |
| 64 | rm -rf "$STATE" && mkdir -p "$STATE" |
| 65 | [ -f "$SNAP/deployment.json" ] && cp "$SNAP/deployment.json" "$STATE/deployment.json" |
| 66 | launch --load-state "$SNAP/state.json" |
| 67 | say "offline Sepolia replica on $RPC from $SNAP (pid $(cat "$PIDF"), block $(block_now), block time ${BT}s; no upstream RPC)" |
| 68 | ;; |
| 69 | stop) |
| 70 | if running; then |
| 71 | P=$(cat "$PIDF") |
| 72 | kill "$P" |
| 73 | for _ in $(seq 1 20); do kill -0 "$P" 2>/dev/null || break; sleep 0.5; done |
| 74 | if kill -0 "$P" 2>/dev/null; then say "chain :$FORK (pid $P) is still shutting down"; else say "stopped chain :$FORK (pid $P)"; fi |
| 75 | else |
| 76 | say "no chain started by this script on :$FORK" |
| 77 | fi |
| 78 | rm -f "$PIDF" |
| 79 | ;; |
| 80 | status) |
| 81 | if running; then say "chain :$FORK running (pid $(cat "$PIDF")), block $(block_now)"; else say "chain :$FORK not running"; exit 1; fi |
| 82 | ;; |
| 83 | *) die "usage: FORK=<port> $0 start|save [dir]|load <dir>|stop|status" ;; |
| 84 | esac |