Merge 2-multichain-check into main
7c6b16b parent: dded29a modified
.gitignore +1 -0 | @@ -5,6 +5,7 @@ out/ | ||
| 5 | 5 | # Ignores development broadcast logs |
| 6 | 6 | !/broadcast |
| 7 | 7 | /broadcast/*/31337/ |
| 8 | +/broadcast/*/31338/ | |
| 8 | 9 | /broadcast/**/dry-run/ |
| 9 | 10 | |
| 10 | 11 | # Docs |
| @@ -5,6 +5,7 @@ out/ | |||
| 5 | # Ignores development broadcast logs | 5 | # Ignores development broadcast logs |
| 6 | !/broadcast | 6 | !/broadcast |
| 7 | /broadcast/*/31337/ | 7 | /broadcast/*/31337/ |
| 8 | +/broadcast/*/31338/ | ||
| 8 | /broadcast/**/dry-run/ | 9 | /broadcast/**/dry-run/ |
| 9 | 10 | ||
| 10 | # Docs | 11 | # Docs |
modified
CHANGELOG.md +2 -0 | @@ -20,5 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||
| 20 | 20 | and CREATE2 tests pinning address determinism across chain IDs. |
| 21 | 21 | - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant |
| 22 | 22 | defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars. |
| 23 | +- `script/multichain-check.sh`, deploying `Love` on two local anvil nodes with | |
| 24 | + different chain IDs and failing if the two addresses don't match. | |
| 23 | 25 | - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`. |
| 24 | 26 | - `.env.example` documenting required environment variables. |
| @@ -20,5 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 20 | and CREATE2 tests pinning address determinism across chain IDs. | 20 | and CREATE2 tests pinning address determinism across chain IDs. |
| 21 | - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant | 21 | - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant |
| 22 | defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars. | 22 | defaults, and Base Sepolia RPC/Etherscan endpoints driven by env vars. |
| 23 | +- `script/multichain-check.sh`, deploying `Love` on two local anvil nodes with | ||
| 24 | + different chain IDs and failing if the two addresses don't match. | ||
| 23 | - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`. | 25 | - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`. |
| 24 | - `.env.example` documenting required environment variables. | 26 | - `.env.example` documenting required environment variables. |
modified
README.md +10 -0 | @@ -33,6 +33,16 @@ forge script script/Love.s.sol:LoveScript \ | ||
| 33 | 33 | --verify |
| 34 | 34 | ``` |
| 35 | 35 | |
| 36 | +To check determinism for real, deploy on two local chains and compare: | |
| 37 | + | |
| 38 | +```shell | |
| 39 | +./script/multichain-check.sh | |
| 40 | +``` | |
| 41 | + | |
| 42 | +It starts two anvil nodes (chain IDs 31337 and 31338), broadcasts the script on | |
| 43 | +both, and exits non-zero if the addresses differ. Override `PORT_A`, `PORT_B`, | |
| 44 | +`CHAIN_ID_A`, `CHAIN_ID_B` or `SALT` as needed. | |
| 45 | + | |
| 36 | 46 | ## Mint |
| 37 | 47 | |
| 38 | 48 | ```shell |
| @@ -33,6 +33,16 @@ forge script script/Love.s.sol:LoveScript \ | |||
| 33 | --verify | 33 | --verify |
| 34 | ``` | 34 | ``` |
| 35 | 35 | ||
| 36 | +To check determinism for real, deploy on two local chains and compare: | ||
| 37 | + | ||
| 38 | +```shell | ||
| 39 | +./script/multichain-check.sh | ||
| 40 | +``` | ||
| 41 | + | ||
| 42 | +It starts two anvil nodes (chain IDs 31337 and 31338), broadcasts the script on | ||
| 43 | +both, and exits non-zero if the addresses differ. Override `PORT_A`, `PORT_B`, | ||
| 44 | +`CHAIN_ID_A`, `CHAIN_ID_B` or `SALT` as needed. | ||
| 45 | + | ||
| 36 | ## Mint | 46 | ## Mint |
| 37 | 47 | ||
| 38 | ```shell | 48 | ```shell |
added
script/multichain-check.sh +121 -0 | new file mode 100755 | ||
| @@ -0,0 +1,121 @@ | ||
| 1 | +#!/usr/bin/env bash | |
| 2 | +# | |
| 3 | +# Deploys Love on two local anvil nodes with different chain ids and checks | |
| 4 | +# that CREATE2 gives the token the same address on both. | |
| 5 | +# | |
| 6 | +# Usage: ./script/multichain-check.sh | |
| 7 | +# | |
| 8 | +# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT | |
| 9 | + | |
| 10 | +set -euo pipefail | |
| 11 | + | |
| 12 | +PORT_A=${PORT_A:-8545} | |
| 13 | +PORT_B=${PORT_B:-8546} | |
| 14 | +CHAIN_ID_A=${CHAIN_ID_A:-31337} | |
| 15 | +CHAIN_ID_B=${CHAIN_ID_B:-31338} | |
| 16 | + | |
| 17 | +# First anvil dev account — local only, never use this key anywhere else. | |
| 18 | +ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | |
| 19 | +CREATE2_FACTORY=0x4e59b44847b379578588920cA78FbF26c0B4956C | |
| 20 | + | |
| 21 | +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) | |
| 22 | +RPC_A="http://127.0.0.1:$PORT_A" | |
| 23 | +RPC_B="http://127.0.0.1:$PORT_B" | |
| 24 | +TMP=$(mktemp -d) | |
| 25 | +PIDS=() | |
| 26 | + | |
| 27 | +cleanup() { | |
| 28 | + for pid in ${PIDS[@]+"${PIDS[@]}"}; do | |
| 29 | + kill "$pid" 2>/dev/null || true | |
| 30 | + done | |
| 31 | + rm -rf "$TMP" | |
| 32 | + rm -rf "$ROOT/broadcast/Love.s.sol/$CHAIN_ID_A" "$ROOT/broadcast/Love.s.sol/$CHAIN_ID_B" | |
| 33 | + rmdir "$ROOT/broadcast/Love.s.sol" "$ROOT/broadcast" 2>/dev/null || true | |
| 34 | +} | |
| 35 | +trap cleanup EXIT | |
| 36 | + | |
| 37 | +start_node() { | |
| 38 | + local port=$1 chain_id=$2 rpc=$3 | |
| 39 | + anvil --port "$port" --chain-id "$chain_id" --silent >"$TMP/anvil-$port.log" 2>&1 & | |
| 40 | + PIDS+=($!) | |
| 41 | + | |
| 42 | + for _ in $(seq 1 50); do | |
| 43 | + if cast chain-id --rpc-url "$rpc" >/dev/null 2>&1; then | |
| 44 | + return 0 | |
| 45 | + fi | |
| 46 | + sleep 0.2 | |
| 47 | + done | |
| 48 | + | |
| 49 | + echo "anvil on port $port never came up:" >&2 | |
| 50 | + cat "$TMP/anvil-$port.log" >&2 | |
| 51 | + exit 1 | |
| 52 | +} | |
| 53 | + | |
| 54 | +deploy() { | |
| 55 | + local rpc=$1 label=$2 | |
| 56 | + forge script script/Love.s.sol:LoveScript \ | |
| 57 | + --rpc-url "$rpc" \ | |
| 58 | + --private-key "$ANVIL_KEY" \ | |
| 59 | + --broadcast \ | |
| 60 | + --skip-simulation >"$TMP/deploy-$label.log" 2>&1 || | |
| 61 | + { | |
| 62 | + echo "deploy on $label failed:" >&2 | |
| 63 | + cat "$TMP/deploy-$label.log" >&2 | |
| 64 | + exit 1 | |
| 65 | + } | |
| 66 | + | |
| 67 | + grep -oE 'predicted +0x[0-9a-fA-F]{40}' "$TMP/deploy-$label.log" | tail -1 | grep -oE '0x[0-9a-fA-F]{40}' | |
| 68 | +} | |
| 69 | + | |
| 70 | +report() { | |
| 71 | + local rpc=$1 addr=$2 label=$3 | |
| 72 | + local code | |
| 73 | + code=$(cast code "$addr" --rpc-url "$rpc") | |
| 74 | + | |
| 75 | + if [[ ${#code} -le 2 ]]; then | |
| 76 | + echo "$label: no code at $addr" >&2 | |
| 77 | + exit 1 | |
| 78 | + fi | |
| 79 | + | |
| 80 | + printf '%s %s %s bytes %s / %s\n' \ | |
| 81 | + "$label" "$addr" "$(((${#code} - 2) / 2))" \ | |
| 82 | + "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ | |
| 83 | + "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" | |
| 84 | +} | |
| 85 | + | |
| 86 | +cd "$ROOT" | |
| 87 | + | |
| 88 | +for cmd in anvil cast forge; do | |
| 89 | + command -v "$cmd" >/dev/null || { | |
| 90 | + echo "$cmd not found — install foundry" >&2 | |
| 91 | + exit 1 | |
| 92 | + } | |
| 93 | +done | |
| 94 | + | |
| 95 | +start_node "$PORT_A" "$CHAIN_ID_A" "$RPC_A" | |
| 96 | +start_node "$PORT_B" "$CHAIN_ID_B" "$RPC_B" | |
| 97 | + | |
| 98 | +for pair in "A|$RPC_A" "B|$RPC_B"; do | |
| 99 | + IFS='|' read -r label rpc <<<"$pair" | |
| 100 | + if [[ $(cast code "$CREATE2_FACTORY" --rpc-url "$rpc") == "0x" ]]; then | |
| 101 | + echo "$label: deterministic deployer missing at $CREATE2_FACTORY" >&2 | |
| 102 | + exit 1 | |
| 103 | + fi | |
| 104 | +done | |
| 105 | + | |
| 106 | +forge build >/dev/null | |
| 107 | + | |
| 108 | +ADDR_A=$(deploy "$RPC_A" A) | |
| 109 | +ADDR_B=$(deploy "$RPC_B" B) | |
| 110 | + | |
| 111 | +echo "chain $CHAIN_ID_A" | |
| 112 | +report "$RPC_A" "$ADDR_A" " A" | |
| 113 | +echo "chain $CHAIN_ID_B" | |
| 114 | +report "$RPC_B" "$ADDR_B" " B" | |
| 115 | + | |
| 116 | +if [[ "$ADDR_A" != "$ADDR_B" ]]; then | |
| 117 | + echo "FAIL: addresses differ" >&2 | |
| 118 | + exit 1 | |
| 119 | +fi | |
| 120 | + | |
| 121 | +echo "OK: same address on both chains" | |
| new file mode 100755 | |||
| @@ -0,0 +1,121 @@ | |||
| 1 | +#!/usr/bin/env bash | ||
| 2 | +# | ||
| 3 | +# Deploys Love on two local anvil nodes with different chain ids and checks | ||
| 4 | +# that CREATE2 gives the token the same address on both. | ||
| 5 | +# | ||
| 6 | +# Usage: ./script/multichain-check.sh | ||
| 7 | +# | ||
| 8 | +# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT | ||
| 9 | + | ||
| 10 | +set -euo pipefail | ||
| 11 | + | ||
| 12 | +PORT_A=${PORT_A:-8545} | ||
| 13 | +PORT_B=${PORT_B:-8546} | ||
| 14 | +CHAIN_ID_A=${CHAIN_ID_A:-31337} | ||
| 15 | +CHAIN_ID_B=${CHAIN_ID_B:-31338} | ||
| 16 | + | ||
| 17 | +# First anvil dev account — local only, never use this key anywhere else. | ||
| 18 | +ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | ||
| 19 | +CREATE2_FACTORY=0x4e59b44847b379578588920cA78FbF26c0B4956C | ||
| 20 | + | ||
| 21 | +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) | ||
| 22 | +RPC_A="http://127.0.0.1:$PORT_A" | ||
| 23 | +RPC_B="http://127.0.0.1:$PORT_B" | ||
| 24 | +TMP=$(mktemp -d) | ||
| 25 | +PIDS=() | ||
| 26 | + | ||
| 27 | +cleanup() { | ||
| 28 | + for pid in ${PIDS[@]+"${PIDS[@]}"}; do | ||
| 29 | + kill "$pid" 2>/dev/null || true | ||
| 30 | + done | ||
| 31 | + rm -rf "$TMP" | ||
| 32 | + rm -rf "$ROOT/broadcast/Love.s.sol/$CHAIN_ID_A" "$ROOT/broadcast/Love.s.sol/$CHAIN_ID_B" | ||
| 33 | + rmdir "$ROOT/broadcast/Love.s.sol" "$ROOT/broadcast" 2>/dev/null || true | ||
| 34 | +} | ||
| 35 | +trap cleanup EXIT | ||
| 36 | + | ||
| 37 | +start_node() { | ||
| 38 | + local port=$1 chain_id=$2 rpc=$3 | ||
| 39 | + anvil --port "$port" --chain-id "$chain_id" --silent >"$TMP/anvil-$port.log" 2>&1 & | ||
| 40 | + PIDS+=($!) | ||
| 41 | + | ||
| 42 | + for _ in $(seq 1 50); do | ||
| 43 | + if cast chain-id --rpc-url "$rpc" >/dev/null 2>&1; then | ||
| 44 | + return 0 | ||
| 45 | + fi | ||
| 46 | + sleep 0.2 | ||
| 47 | + done | ||
| 48 | + | ||
| 49 | + echo "anvil on port $port never came up:" >&2 | ||
| 50 | + cat "$TMP/anvil-$port.log" >&2 | ||
| 51 | + exit 1 | ||
| 52 | +} | ||
| 53 | + | ||
| 54 | +deploy() { | ||
| 55 | + local rpc=$1 label=$2 | ||
| 56 | + forge script script/Love.s.sol:LoveScript \ | ||
| 57 | + --rpc-url "$rpc" \ | ||
| 58 | + --private-key "$ANVIL_KEY" \ | ||
| 59 | + --broadcast \ | ||
| 60 | + --skip-simulation >"$TMP/deploy-$label.log" 2>&1 || | ||
| 61 | + { | ||
| 62 | + echo "deploy on $label failed:" >&2 | ||
| 63 | + cat "$TMP/deploy-$label.log" >&2 | ||
| 64 | + exit 1 | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + grep -oE 'predicted +0x[0-9a-fA-F]{40}' "$TMP/deploy-$label.log" | tail -1 | grep -oE '0x[0-9a-fA-F]{40}' | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +report() { | ||
| 71 | + local rpc=$1 addr=$2 label=$3 | ||
| 72 | + local code | ||
| 73 | + code=$(cast code "$addr" --rpc-url "$rpc") | ||
| 74 | + | ||
| 75 | + if [[ ${#code} -le 2 ]]; then | ||
| 76 | + echo "$label: no code at $addr" >&2 | ||
| 77 | + exit 1 | ||
| 78 | + fi | ||
| 79 | + | ||
| 80 | + printf '%s %s %s bytes %s / %s\n' \ | ||
| 81 | + "$label" "$addr" "$(((${#code} - 2) / 2))" \ | ||
| 82 | + "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ | ||
| 83 | + "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +cd "$ROOT" | ||
| 87 | + | ||
| 88 | +for cmd in anvil cast forge; do | ||
| 89 | + command -v "$cmd" >/dev/null || { | ||
| 90 | + echo "$cmd not found — install foundry" >&2 | ||
| 91 | + exit 1 | ||
| 92 | + } | ||
| 93 | +done | ||
| 94 | + | ||
| 95 | +start_node "$PORT_A" "$CHAIN_ID_A" "$RPC_A" | ||
| 96 | +start_node "$PORT_B" "$CHAIN_ID_B" "$RPC_B" | ||
| 97 | + | ||
| 98 | +for pair in "A|$RPC_A" "B|$RPC_B"; do | ||
| 99 | + IFS='|' read -r label rpc <<<"$pair" | ||
| 100 | + if [[ $(cast code "$CREATE2_FACTORY" --rpc-url "$rpc") == "0x" ]]; then | ||
| 101 | + echo "$label: deterministic deployer missing at $CREATE2_FACTORY" >&2 | ||
| 102 | + exit 1 | ||
| 103 | + fi | ||
| 104 | +done | ||
| 105 | + | ||
| 106 | +forge build >/dev/null | ||
| 107 | + | ||
| 108 | +ADDR_A=$(deploy "$RPC_A" A) | ||
| 109 | +ADDR_B=$(deploy "$RPC_B" B) | ||
| 110 | + | ||
| 111 | +echo "chain $CHAIN_ID_A" | ||
| 112 | +report "$RPC_A" "$ADDR_A" " A" | ||
| 113 | +echo "chain $CHAIN_ID_B" | ||
| 114 | +report "$RPC_B" "$ADDR_B" " B" | ||
| 115 | + | ||
| 116 | +if [[ "$ADDR_A" != "$ADDR_B" ]]; then | ||
| 117 | + echo "FAIL: addresses differ" >&2 | ||
| 118 | + exit 1 | ||
| 119 | +fi | ||
| 120 | + | ||
| 121 | +echo "OK: same address on both chains" | ||