julien/lovepublic Fork 0
7c6b16b
Commits
Clone
git clone https://git.rickub.com/julien/love.git
git clone ssh://git@rickub.com/julien/love.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Merge 2-multichain-check into main

rickub committed 2026-09-17T19:19:19Z Browse files
7c6b16b parent: dded29a
modified .gitignore +1 -0
@@ -5,6 +5,7 @@ out/
55 # Ignores development broadcast logs
66 !/broadcast
77 /broadcast/*/31337/
8+/broadcast/*/31338/
89 /broadcast/**/dry-run/
910
1011 # Docs
@@ -5,6 +5,7 @@ out/
5 # Ignores development broadcast logs5 # Ignores development broadcast logs
6 !/broadcast6 !/broadcast
7 /broadcast/*/31337/7 /broadcast/*/31337/
8+/broadcast/*/31338/
8 /broadcast/**/dry-run/9 /broadcast/**/dry-run/
9 10
10 # Docs11 # Docs
modified CHANGELOG.md +2 -0
@@ -20,5 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020 and CREATE2 tests pinning address determinism across chain IDs.
2121 - `foundry.toml` with pinned solc 0.8.30, optimizer, fmt rules, fuzz/invariant
2222 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.
2325 - `remappings.txt` for `@openzeppelin/contracts/` and `forge-std/`.
2426 - `.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/invariant21 - `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 \
3333 --verify
3434 ```
3535
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+
3646 ## Mint
3747
3848 ```shell
@@ -33,6 +33,16 @@ forge script script/Love.s.sol:LoveScript \
33 --verify33 --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 ## Mint46 ## Mint
37 47
38 ```shell48 ```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"