| @@ -1,13 +1,22 @@ |
| 1 | #!/usr/bin/env bash | 1 | #!/usr/bin/env bash |
| 2 | # | 2 | # |
| 3 | -# Deploys Love on two local anvil nodes with different chain ids and checks | 3 | +# Proves the claim the registry exists to make: LOVE lands at the same address |
| 4 | -# that CREATE2 gives the token the same address on both. The address embeds the | 4 | +# on two chains that have nothing about their wETH in common. |
| 5 | -# wETH address, so this only holds for chains sharing one wETH deployment — | 5 | +# |
| 6 | -# both nodes here are given the same WETH. | 6 | +# Two anvil nodes with different chain ids get different wETH implementations, |
| | 7 | +# at different addresses — node A the OP Stack predeploy, node B canonical |
| | 8 | +# WETH9 at its mainnet address — using the same runtime bytecode the tests |
| | 9 | +# etch. Each node is then brought up the way a real chain would be: registry, |
| | 10 | +# registration, token. If the two token addresses match, wETH is genuinely out |
| | 11 | +# of the creation code. |
| | 12 | +# |
| | 13 | +# The previous version of this script gave both nodes the same wETH, because |
| | 14 | +# back then the address was a constructor argument and matching was only |
| | 15 | +# possible when the argument matched. That is exactly what changed. |
| 7 | # | 16 | # |
| 8 | # Usage: ./script/multichain-check.sh | 17 | # Usage: ./script/multichain-check.sh |
| 9 | # | 18 | # |
| 10 | -# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT, WETH | 19 | +# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT |
| 11 | | 20 | |
| 12 | set -euo pipefail | 21 | set -euo pipefail |
| 13 | | 22 | |
| @@ -20,7 +29,15 @@ CHAIN_ID_B=${CHAIN_ID_B:-31338} |
| 20 | ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | 29 | ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 |
| 21 | CREATE2_FACTORY=0x4e59b44847b379578588920cA78FbF26c0B4956C | 30 | CREATE2_FACTORY=0x4e59b44847b379578588920cA78FbF26c0B4956C |
| 22 | | 31 | |
| | 32 | +# Node A: the OP Stack predeploy. Node B: canonical WETH9, where it sits on |
| | 33 | +# Ethereum. Different implementations, different addresses, both allowlisted. |
| | 34 | +WETH_A=0x4200000000000000000000000000000000000006 |
| | 35 | +WETH_B=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| | 36 | + |
| 23 | ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) | 37 | ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) |
| | 38 | +FIXTURE_A="$ROOT/test/fixtures/weth9-op-legacy.hex" |
| | 39 | +FIXTURE_B="$ROOT/test/fixtures/weth9-canonical.hex" |
| | 40 | + |
| 24 | RPC_A="http://127.0.0.1:$PORT_A" | 41 | RPC_A="http://127.0.0.1:$PORT_A" |
| 25 | RPC_B="http://127.0.0.1:$PORT_B" | 42 | RPC_B="http://127.0.0.1:$PORT_B" |
| 26 | TMP=$(mktemp -d) | 43 | TMP=$(mktemp -d) |
| @@ -53,9 +70,29 @@ start_node() { |
| 53 | exit 1 | 70 | exit 1 |
| 54 | } | 71 | } |
| 55 | | 72 | |
| | 73 | +# Puts real wETH runtime bytecode at an address, the way the chain itself would |
| | 74 | +# have it. The registry checks EXTCODEHASH, so only genuine bytecode passes — |
| | 75 | +# there is no test-only path through the allowlist. |
| | 76 | +place_weth() { |
| | 77 | + local rpc=$1 addr=$2 fixture=$3 label=$4 |
| | 78 | + |
| | 79 | + [[ -f $fixture ]] || { |
| | 80 | + echo "missing fixture $fixture" >&2 |
| | 81 | + exit 1 |
| | 82 | + } |
| | 83 | + |
| | 84 | + cast rpc anvil_setCode "$addr" "$(tr -d '\n' <"$fixture")" --rpc-url "$rpc" >/dev/null |
| | 85 | + |
| | 86 | + if [[ $(cast code "$addr" --rpc-url "$rpc") == "0x" ]]; then |
| | 87 | + echo "$label: failed to place wETH at $addr" >&2 |
| | 88 | + exit 1 |
| | 89 | + fi |
| | 90 | +} |
| | 91 | + |
| 56 | deploy() { | 92 | deploy() { |
| 57 | - local rpc=$1 label=$2 | 93 | + local rpc=$1 label=$2 weth=$3 |
| 58 | - forge script script/Love.s.sol:LoveScript \ | 94 | + |
| | 95 | + WETH=$weth forge script script/Love.s.sol:LoveScript \ |
| 59 | --rpc-url "$rpc" \ | 96 | --rpc-url "$rpc" \ |
| 60 | --private-key "$ANVIL_KEY" \ | 97 | --private-key "$ANVIL_KEY" \ |
| 61 | --broadcast \ | 98 | --broadcast \ |
| @@ -72,6 +109,7 @@ deploy() { |
| 72 | report() { | 109 | report() { |
| 73 | local rpc=$1 addr=$2 label=$3 | 110 | local rpc=$1 addr=$2 label=$3 |
| 74 | local code | 111 | local code |
| | 112 | + |
| 75 | code=$(cast code "$addr" --rpc-url "$rpc") | 113 | code=$(cast code "$addr" --rpc-url "$rpc") |
| 76 | | 114 | |
| 77 | if [[ ${#code} -le 2 ]]; then | 115 | if [[ ${#code} -le 2 ]]; then |
| @@ -79,11 +117,12 @@ report() { |
| 79 | exit 1 | 117 | exit 1 |
| 80 | fi | 118 | fi |
| 81 | | 119 | |
| 82 | - printf '%s %s %s bytes %s / %s weth %s\n' \ | 120 | + printf '%s %s %s bytes %s / %s\n' \ |
| 83 | "$label" "$addr" "$(((${#code} - 2) / 2))" \ | 121 | "$label" "$addr" "$(((${#code} - 2) / 2))" \ |
| 84 | "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ | 122 | "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ |
| 85 | - "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" \ | 123 | + "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" |
| 86 | - "$(cast call "$addr" 'WETH()(address)' --rpc-url "$rpc")" | 124 | + printf '%s registry %s\n' "$label" "$(cast call "$addr" 'REGISTRY()(address)' --rpc-url "$rpc")" |
| | 125 | + printf '%s weth %s\n' "$label" "$(cast call "$addr" 'WETH()(address)' --rpc-url "$rpc")" |
| 87 | } | 126 | } |
| 88 | | 127 | |
| 89 | cd "$ROOT" | 128 | cd "$ROOT" |
| @@ -106,19 +145,37 @@ for pair in "A|$RPC_A" "B|$RPC_B"; do |
| 106 | fi | 145 | fi |
| 107 | done | 146 | done |
| 108 | | 147 | |
| 109 | -forge build >/dev/null | 148 | +forge build >"$TMP/build.log" 2>&1 || { |
| | 149 | + cat "$TMP/build.log" >&2 |
| | 150 | + exit 1 |
| | 151 | +} |
| | 152 | + |
| | 153 | +place_weth "$RPC_A" "$WETH_A" "$FIXTURE_A" A |
| | 154 | +place_weth "$RPC_B" "$WETH_B" "$FIXTURE_B" B |
| 110 | | 155 | |
| 111 | -ADDR_A=$(deploy "$RPC_A" A) | 156 | +ADDR_A=$(deploy "$RPC_A" A "$WETH_A") |
| 112 | -ADDR_B=$(deploy "$RPC_B" B) | 157 | +ADDR_B=$(deploy "$RPC_B" B "$WETH_B") |
| 113 | | 158 | |
| 114 | -echo "chain $CHAIN_ID_A" | 159 | +echo "chain $CHAIN_ID_A — OP Stack legacy WETH9 at $WETH_A" |
| 115 | report "$RPC_A" "$ADDR_A" " A" | 160 | report "$RPC_A" "$ADDR_A" " A" |
| 116 | -echo "chain $CHAIN_ID_B" | 161 | +echo "chain $CHAIN_ID_B — canonical WETH9 at $WETH_B" |
| 117 | report "$RPC_B" "$ADDR_B" " B" | 162 | report "$RPC_B" "$ADDR_B" " B" |
| | 163 | +echo |
| | 164 | + |
| | 165 | +# macOS ships bash 3.2, which has no ${var,,}. |
| | 166 | +lower() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]'; } |
| | 167 | + |
| | 168 | +WETH_ON_A=$(cast call "$ADDR_A" 'WETH()(address)' --rpc-url "$RPC_A") |
| | 169 | +WETH_ON_B=$(cast call "$ADDR_B" 'WETH()(address)' --rpc-url "$RPC_B") |
| | 170 | + |
| | 171 | +if [[ $(lower "$WETH_ON_A") == "$(lower "$WETH_ON_B")" ]]; then |
| | 172 | + echo "FAIL: both chains ended up on the same wETH — this proves nothing" >&2 |
| | 173 | + exit 1 |
| | 174 | +fi |
| 118 | | 175 | |
| 119 | -if [[ "$ADDR_A" != "$ADDR_B" ]]; then | 176 | +if [[ $(lower "$ADDR_A") != "$(lower "$ADDR_B")" ]]; then |
| 120 | echo "FAIL: addresses differ" >&2 | 177 | echo "FAIL: addresses differ" >&2 |
| 121 | exit 1 | 178 | exit 1 |
| 122 | fi | 179 | fi |
| 123 | | 180 | |
| 124 | -echo "OK: same address on both chains" | 181 | +echo "OK: same LOVE address on both chains, backed by different wETH" |