#!/usr/bin/env bash # # Proves the claim the registry exists to make: LOVE lands at the same address # on two chains that have nothing about their wETH in common. # # Two anvil nodes with different chain ids get different wETH implementations, # at different addresses — node A the OP Stack predeploy, node B canonical # WETH9 at its mainnet address — using the same runtime bytecode the tests # etch. Each node is then brought up the way a real chain would be: registry, # registration, token. If the two token addresses match, wETH is genuinely out # of the creation code. # # The previous version of this script gave both nodes the same wETH, because # back then the address was a constructor argument and matching was only # possible when the argument matched. That is exactly what changed. # # Usage: ./script/multichain-check.sh # # Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT set -euo pipefail PORT_A=${PORT_A:-8545} PORT_B=${PORT_B:-8546} CHAIN_ID_A=${CHAIN_ID_A:-31337} CHAIN_ID_B=${CHAIN_ID_B:-31338} # First anvil dev account — local only, never use this key anywhere else. ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 CREATE2_FACTORY=0x4e59b44847b379578588920cA78FbF26c0B4956C # Node A: the OP Stack predeploy. Node B: canonical WETH9, where it sits on # Ethereum. Different implementations, different addresses, both allowlisted. WETH_A=0x4200000000000000000000000000000000000006 WETH_B=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) FIXTURE_A="$ROOT/test/fixtures/weth9-op-legacy.hex" FIXTURE_B="$ROOT/test/fixtures/weth9-canonical.hex" RPC_A="http://127.0.0.1:$PORT_A" RPC_B="http://127.0.0.1:$PORT_B" TMP=$(mktemp -d) PIDS=() cleanup() { for pid in ${PIDS[@]+"${PIDS[@]}"}; do kill "$pid" 2>/dev/null || true done rm -rf "$TMP" rm -rf "$ROOT/broadcast/Love.s.sol/$CHAIN_ID_A" "$ROOT/broadcast/Love.s.sol/$CHAIN_ID_B" rmdir "$ROOT/broadcast/Love.s.sol" "$ROOT/broadcast" 2>/dev/null || true } trap cleanup EXIT start_node() { local port=$1 chain_id=$2 rpc=$3 anvil --port "$port" --chain-id "$chain_id" --silent >"$TMP/anvil-$port.log" 2>&1 & PIDS+=($!) for _ in $(seq 1 50); do if cast chain-id --rpc-url "$rpc" >/dev/null 2>&1; then return 0 fi sleep 0.2 done echo "anvil on port $port never came up:" >&2 cat "$TMP/anvil-$port.log" >&2 exit 1 } # Puts real wETH runtime bytecode at an address, the way the chain itself would # have it. The registry checks EXTCODEHASH, so only genuine bytecode passes — # there is no test-only path through the allowlist. place_weth() { local rpc=$1 addr=$2 fixture=$3 label=$4 [[ -f $fixture ]] || { echo "missing fixture $fixture" >&2 exit 1 } cast rpc anvil_setCode "$addr" "$(tr -d '\n' <"$fixture")" --rpc-url "$rpc" >/dev/null if [[ $(cast code "$addr" --rpc-url "$rpc") == "0x" ]]; then echo "$label: failed to place wETH at $addr" >&2 exit 1 fi } deploy() { local rpc=$1 label=$2 weth=$3 WETH=$weth forge script script/Love.s.sol:LoveScript \ --rpc-url "$rpc" \ --private-key "$ANVIL_KEY" \ --broadcast \ --skip-simulation >"$TMP/deploy-$label.log" 2>&1 || { echo "deploy on $label failed:" >&2 cat "$TMP/deploy-$label.log" >&2 exit 1 } grep -oE 'predicted +0x[0-9a-fA-F]{40}' "$TMP/deploy-$label.log" | tail -1 | grep -oE '0x[0-9a-fA-F]{40}' } report() { local rpc=$1 addr=$2 label=$3 local code code=$(cast code "$addr" --rpc-url "$rpc") if [[ ${#code} -le 2 ]]; then echo "$label: no code at $addr" >&2 exit 1 fi printf '%s %s %s bytes %s / %s\n' \ "$label" "$addr" "$(((${#code} - 2) / 2))" \ "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" printf '%s registry %s\n' "$label" "$(cast call "$addr" 'REGISTRY()(address)' --rpc-url "$rpc")" printf '%s weth %s\n' "$label" "$(cast call "$addr" 'WETH()(address)' --rpc-url "$rpc")" } cd "$ROOT" for cmd in anvil cast forge; do command -v "$cmd" >/dev/null || { echo "$cmd not found — install foundry" >&2 exit 1 } done start_node "$PORT_A" "$CHAIN_ID_A" "$RPC_A" start_node "$PORT_B" "$CHAIN_ID_B" "$RPC_B" for pair in "A|$RPC_A" "B|$RPC_B"; do IFS='|' read -r label rpc <<<"$pair" if [[ $(cast code "$CREATE2_FACTORY" --rpc-url "$rpc") == "0x" ]]; then echo "$label: deterministic deployer missing at $CREATE2_FACTORY" >&2 exit 1 fi done forge build >"$TMP/build.log" 2>&1 || { cat "$TMP/build.log" >&2 exit 1 } place_weth "$RPC_A" "$WETH_A" "$FIXTURE_A" A place_weth "$RPC_B" "$WETH_B" "$FIXTURE_B" B ADDR_A=$(deploy "$RPC_A" A "$WETH_A") ADDR_B=$(deploy "$RPC_B" B "$WETH_B") echo "chain $CHAIN_ID_A — OP Stack legacy WETH9 at $WETH_A" report "$RPC_A" "$ADDR_A" " A" echo "chain $CHAIN_ID_B — canonical WETH9 at $WETH_B" report "$RPC_B" "$ADDR_B" " B" echo # macOS ships bash 3.2, which has no ${var,,}. lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; } WETH_ON_A=$(cast call "$ADDR_A" 'WETH()(address)' --rpc-url "$RPC_A") WETH_ON_B=$(cast call "$ADDR_B" 'WETH()(address)' --rpc-url "$RPC_B") if [[ $(lower "$WETH_ON_A") == "$(lower "$WETH_ON_B")" ]]; then echo "FAIL: both chains ended up on the same wETH — this proves nothing" >&2 exit 1 fi if [[ $(lower "$ADDR_A") != "$(lower "$ADDR_B")" ]]; then echo "FAIL: addresses differ" >&2 exit 1 fi echo "OK: same LOVE address on both chains, backed by different wETH"