julien/lovepublic Fork 0
5a80e08
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.

add weth bytecode survey script

julienbrg committed 2026-09-17T22:53:33+02:00 Browse files
5a80e08 parent: 6b4a40d
added script/weth-chains.tsv +36 -0
new file mode 100644
@@ -0,0 +1,36 @@
1+# The wETH deployment on each chain, and a public RPC to read it from.
2+#
3+# Input to ./weth-codehashes.sh, which hashes the code at each address to find
4+# out how many distinct wETH implementations are actually in use. Entries are
5+# claims, not facts: the script calls name/symbol/decimals on each one and
6+# flags anything that does not answer like wETH, so a wrong address here shows
7+# up as a bad row rather than as a bogus hash in the allowlist.
8+#
9+# chain_id name weth rpc
10+1 ethereum 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 https://ethereum-rpc.publicnode.com
11+10 optimism 0x4200000000000000000000000000000000000006 https://optimism-rpc.publicnode.com
12+56 bnb-chain 0x2170Ed0880ac9A755fd29B2688956BD959F933F8 https://bsc-rpc.publicnode.com
13+100 gnosis 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1 https://gnosis-rpc.publicnode.com
14+130 unichain 0x4200000000000000000000000000000000000006 https://unichain-rpc.publicnode.com
15+137 polygon 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 https://polygon-bor-rpc.publicnode.com
16+204 opbnb 0xE7798f023fC62146e8Aa1b36Da45fb70855a77Ea https://opbnb-rpc.publicnode.com
17+252 fraxtal 0xFC00000000000000000000000000000000000006 https://fraxtal.drpc.org
18+324 zksync-era 0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91 https://mainnet.era.zksync.io
19+480 world-chain 0x4200000000000000000000000000000000000006 https://worldchain-mainnet.g.alchemy.com/public
20+1088 metis 0x420000000000000000000000000000000000000A https://andromeda.metis.io/?owner=1088
21+1101 polygon-zkevm 0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9 https://polygon-zkevm.drpc.org
22+1135 lisk 0x4200000000000000000000000000000000000006 https://rpc.api.lisk.com
23+1868 soneium 0x4200000000000000000000000000000000000006 https://rpc.soneium.org
24+5000 mantle 0xdEAddEaDdeadDEadDEADDEAdDEADDEaDDEAD1111 https://rpc.mantle.xyz
25+8453 base 0x4200000000000000000000000000000000000006 https://base-rpc.publicnode.com
26+34443 mode 0x4200000000000000000000000000000000000006 https://mode.drpc.org
27+42161 arbitrum-one 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 https://arbitrum-one-rpc.publicnode.com
28+42220 celo 0xD221812de1BD094f35587EE8E174B07B6167D9Af https://forno.celo.org
29+43114 avalanche 0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB https://avalanche-c-chain-rpc.publicnode.com
30+57073 ink 0x4200000000000000000000000000000000000006 https://ink.drpc.org
31+59144 linea 0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f https://rpc.linea.build
32+80094 berachain 0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590 https://rpc.berachain.com
33+81457 blast 0x4300000000000000000000000000000000000004 https://blast.drpc.org
34+167000 taiko 0xA51894664A773981C6C112C43ce576f315d5b1B6 https://rpc.mainnet.taiko.xyz
35+534352 scroll 0x5300000000000000000000000000000000000004 https://scroll.drpc.org
36+7777777 zora 0x4200000000000000000000000000000000000006 https://rpc.zora.energy
new file mode 100644
@@ -0,0 +1,36 @@
1+# The wETH deployment on each chain, and a public RPC to read it from.
2+#
3+# Input to ./weth-codehashes.sh, which hashes the code at each address to find
4+# out how many distinct wETH implementations are actually in use. Entries are
5+# claims, not facts: the script calls name/symbol/decimals on each one and
6+# flags anything that does not answer like wETH, so a wrong address here shows
7+# up as a bad row rather than as a bogus hash in the allowlist.
8+#
9+# chain_id name weth rpc
10+1 ethereum 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 https://ethereum-rpc.publicnode.com
11+10 optimism 0x4200000000000000000000000000000000000006 https://optimism-rpc.publicnode.com
12+56 bnb-chain 0x2170Ed0880ac9A755fd29B2688956BD959F933F8 https://bsc-rpc.publicnode.com
13+100 gnosis 0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1 https://gnosis-rpc.publicnode.com
14+130 unichain 0x4200000000000000000000000000000000000006 https://unichain-rpc.publicnode.com
15+137 polygon 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 https://polygon-bor-rpc.publicnode.com
16+204 opbnb 0xE7798f023fC62146e8Aa1b36Da45fb70855a77Ea https://opbnb-rpc.publicnode.com
17+252 fraxtal 0xFC00000000000000000000000000000000000006 https://fraxtal.drpc.org
18+324 zksync-era 0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91 https://mainnet.era.zksync.io
19+480 world-chain 0x4200000000000000000000000000000000000006 https://worldchain-mainnet.g.alchemy.com/public
20+1088 metis 0x420000000000000000000000000000000000000A https://andromeda.metis.io/?owner=1088
21+1101 polygon-zkevm 0x4F9A0e7FD2Bf6067db6994CF12E4495Df938E6e9 https://polygon-zkevm.drpc.org
22+1135 lisk 0x4200000000000000000000000000000000000006 https://rpc.api.lisk.com
23+1868 soneium 0x4200000000000000000000000000000000000006 https://rpc.soneium.org
24+5000 mantle 0xdEAddEaDdeadDEadDEADDEAdDEADDEaDDEAD1111 https://rpc.mantle.xyz
25+8453 base 0x4200000000000000000000000000000000000006 https://base-rpc.publicnode.com
26+34443 mode 0x4200000000000000000000000000000000000006 https://mode.drpc.org
27+42161 arbitrum-one 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 https://arbitrum-one-rpc.publicnode.com
28+42220 celo 0xD221812de1BD094f35587EE8E174B07B6167D9Af https://forno.celo.org
29+43114 avalanche 0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB https://avalanche-c-chain-rpc.publicnode.com
30+57073 ink 0x4200000000000000000000000000000000000006 https://ink.drpc.org
31+59144 linea 0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f https://rpc.linea.build
32+80094 berachain 0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590 https://rpc.berachain.com
33+81457 blast 0x4300000000000000000000000000000000000004 https://blast.drpc.org
34+167000 taiko 0xA51894664A773981C6C112C43ce576f315d5b1B6 https://rpc.mainnet.taiko.xyz
35+534352 scroll 0x5300000000000000000000000000000000000004 https://scroll.drpc.org
36+7777777 zora 0x4200000000000000000000000000000000000006 https://rpc.zora.energy
added script/weth-codehashes.sh +163 -0
new file mode 100755
@@ -0,0 +1,163 @@
1+#!/usr/bin/env bash
2+#
3+# Surveys the wETH deployment on every chain in weth-chains.tsv and groups them
4+# by implementation, to answer the question the allowlist in
5+# src/WETHRegistry.sol depends on: how many distinct wETH implementations are
6+# actually out there?
7+#
8+# For each chain it reports three things:
9+#
10+# codehash keccak256 of the runtime code — exactly what EXTCODEHASH returns
11+# and what the registry compares against, so these values are the
12+# allowlist entries verbatim.
13+# family the same hash with solc's trailing metadata blob removed. That
14+# blob is never executed and embeds a hash of the source path and
15+# comments, so one implementation compiled twice gets two
16+# codehashes but one family. Grouping by family is how the exact
17+# hashes get reviewed together; the registry itself never strips.
18+# kind whether the code carries the ERC-20 and wrapper entrypoints
19+# itself. A bridged-ETH token has no deposit/withdraw, and a proxy
20+# has no entrypoints at all — its codehash commits to a forwarder,
21+# not to behaviour, so it must never be allowlisted.
22+#
23+# Chains are read with eth_getCode and hashed locally rather than with
24+# eth_getProof, which several public RPCs do not serve.
25+#
26+# Usage: ./script/weth-codehashes.sh [chains.tsv]
27+#
28+# Exits non-zero if any row is unreachable, so a partial survey cannot be
29+# mistaken for a clean one.
30+
31+set -uo pipefail
32+
33+ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
34+CHAINS=${1:-$ROOT/script/weth-chains.tsv}
35+
36+for cmd in cast; do
37+ command -v "$cmd" >/dev/null || {
38+ echo "$cmd not found — install foundry" >&2
39+ exit 1
40+ }
41+done
42+
43+[[ -f $CHAINS ]] || {
44+ echo "no chain table at $CHAINS" >&2
45+ exit 1
46+}
47+
48+# ERC-20 plus the two wrapper entrypoints. Code missing any of these is not a
49+# wETH this project can peg to, whatever it is called.
50+SELECTORS=(
51+ d0e30db0 # deposit()
52+ 2e1a7d4d # withdraw(uint256)
53+ a9059cbb # transfer(address,uint256)
54+ 70a08231 # balanceOf(address)
55+ 095ea7b3 # approve(address,uint256)
56+ 23b872dd # transferFrom(address,address,uint256)
57+)
58+
59+# Drops solc's metadata blob: the last two bytes hold its length, and the blob
60+# itself starts with a CBOR map (0xa1, 0xa2, …). Anything that fails those two
61+# sanity checks is left whole rather than guessed at.
62+strip_metadata() {
63+ local hex=$1 len total marker
64+ total=$((${#hex} / 2))
65+ [[ ${#hex} -gt 4 ]] || {
66+ printf '%s' "$hex"
67+ return
68+ }
69+
70+ len=$((16#${hex: -4}))
71+ if ((len + 2 >= total || len == 0)); then
72+ printf '%s' "$hex"
73+ return
74+ fi
75+
76+ marker=${hex:$((${#hex} - (len + 2) * 2)):2}
77+ if [[ $marker != a* ]]; then
78+ printf '%s' "$hex"
79+ return
80+ fi
81+
82+ printf '%s' "${hex:0:$((${#hex} - (len + 2) * 2))}"
83+}
84+
85+rows=()
86+unreachable=0
87+
88+printf '%-16s %-9s %-8s %-12s %s\n' CHAIN ID BYTES KIND CODEHASH
89+
90+while IFS=$'\t' read -r chain_id name weth rpc; do
91+ [[ -z ${chain_id:-} || $chain_id == \#* ]] && continue
92+
93+ code=$(cast code "$weth" --rpc-url "$rpc" 2>/dev/null)
94+
95+ if [[ ! $code == 0x* || ${#code} -le 4 ]]; then
96+ printf '%-16s %-9s %-8s %-12s %s\n' "$name" "$chain_id" "-" "-" "UNREACHABLE or no code"
97+ unreachable=$((unreachable + 1))
98+ continue
99+ fi
100+
101+ hex=${code#0x}
102+ body=$(strip_metadata "$hex")
103+
104+ codehash=$(cast keccak "0x$hex")
105+ family=$(cast keccak "0x$body")
106+
107+ kind=wrapper
108+ for selector in "${SELECTORS[@]}"; do
109+ [[ $body == *"$selector"* ]] || {
110+ kind=not-wETH
111+ break
112+ }
113+ done
114+
115+ printf '%-16s %-9s %-8s %-12s %s\n' "$name" "$chain_id" "$((${#hex} / 2))" "$kind" "$codehash"
116+ rows+=("$family|$codehash|$name|$kind|$((${#hex} / 2))")
117+done <"$CHAINS"
118+
119+echo
120+echo "families — one block per implementation, indented lines are the allowlist entries"
121+echo
122+
123+wrapper_chains=0
124+wrapper_hashes=0
125+wrapper_families=0
126+
127+for family in $(printf '%s\n' ${rows[@]+"${rows[@]}"} | cut -d'|' -f1 | sort -u); do
128+ block=$(printf '%s\n' ${rows[@]+"${rows[@]}"} | grep "^$family|")
129+ kinds=$(printf '%s\n' "$block" | cut -d'|' -f4 | sort -u | paste -sd, -)
130+ size=$(printf '%s\n' "$block" | head -1 | cut -d'|' -f5)
131+ chains=$(printf '%s\n' "$block" | wc -l | tr -d ' ')
132+
133+ printf '%s %s bytes %s chain(s) %s\n' "${family:0:18}" "$size" "$chains" "$kinds"
134+
135+ for codehash in $(printf '%s\n' "$block" | cut -d'|' -f2 | sort -u); do
136+ names=$(printf '%s\n' "$block" | grep "|$codehash|" | cut -d'|' -f3 | sort | paste -sd' ' -)
137+ printf ' %s %s\n' "$codehash" "$names"
138+
139+ if [[ $kinds == wrapper ]]; then
140+ wrapper_hashes=$((wrapper_hashes + 1))
141+ fi
142+ done
143+
144+ if [[ $kinds == wrapper ]]; then
145+ wrapper_families=$((wrapper_families + 1))
146+ wrapper_chains=$((wrapper_chains + chains))
147+ fi
148+
149+ echo
150+done
151+
152+printf '%s chains read, %s unreachable\n' "${#rows[@]}" "$unreachable"
153+printf 'wrappers: %s chains, %s families, %s exact codehashes to allowlist\n' \
154+ "$wrapper_chains" "$wrapper_families" "$wrapper_hashes"
155+echo
156+echo "A wrapper family is a candidate, not an entry — read its source before"
157+echo "adding the hash. Proxies and bridged-ETH tokens show up as not-wETH."
158+
159+[[ $unreachable -eq 0 ]] || {
160+ echo >&2
161+ echo "$unreachable row(s) unreachable — survey is incomplete" >&2
162+ exit 1
163+}
new file mode 100755
@@ -0,0 +1,163 @@
1+#!/usr/bin/env bash
2+#
3+# Surveys the wETH deployment on every chain in weth-chains.tsv and groups them
4+# by implementation, to answer the question the allowlist in
5+# src/WETHRegistry.sol depends on: how many distinct wETH implementations are
6+# actually out there?
7+#
8+# For each chain it reports three things:
9+#
10+# codehash keccak256 of the runtime code — exactly what EXTCODEHASH returns
11+# and what the registry compares against, so these values are the
12+# allowlist entries verbatim.
13+# family the same hash with solc's trailing metadata blob removed. That
14+# blob is never executed and embeds a hash of the source path and
15+# comments, so one implementation compiled twice gets two
16+# codehashes but one family. Grouping by family is how the exact
17+# hashes get reviewed together; the registry itself never strips.
18+# kind whether the code carries the ERC-20 and wrapper entrypoints
19+# itself. A bridged-ETH token has no deposit/withdraw, and a proxy
20+# has no entrypoints at all — its codehash commits to a forwarder,
21+# not to behaviour, so it must never be allowlisted.
22+#
23+# Chains are read with eth_getCode and hashed locally rather than with
24+# eth_getProof, which several public RPCs do not serve.
25+#
26+# Usage: ./script/weth-codehashes.sh [chains.tsv]
27+#
28+# Exits non-zero if any row is unreachable, so a partial survey cannot be
29+# mistaken for a clean one.
30+
31+set -uo pipefail
32+
33+ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
34+CHAINS=${1:-$ROOT/script/weth-chains.tsv}
35+
36+for cmd in cast; do
37+ command -v "$cmd" >/dev/null || {
38+ echo "$cmd not found — install foundry" >&2
39+ exit 1
40+ }
41+done
42+
43+[[ -f $CHAINS ]] || {
44+ echo "no chain table at $CHAINS" >&2
45+ exit 1
46+}
47+
48+# ERC-20 plus the two wrapper entrypoints. Code missing any of these is not a
49+# wETH this project can peg to, whatever it is called.
50+SELECTORS=(
51+ d0e30db0 # deposit()
52+ 2e1a7d4d # withdraw(uint256)
53+ a9059cbb # transfer(address,uint256)
54+ 70a08231 # balanceOf(address)
55+ 095ea7b3 # approve(address,uint256)
56+ 23b872dd # transferFrom(address,address,uint256)
57+)
58+
59+# Drops solc's metadata blob: the last two bytes hold its length, and the blob
60+# itself starts with a CBOR map (0xa1, 0xa2, …). Anything that fails those two
61+# sanity checks is left whole rather than guessed at.
62+strip_metadata() {
63+ local hex=$1 len total marker
64+ total=$((${#hex} / 2))
65+ [[ ${#hex} -gt 4 ]] || {
66+ printf '%s' "$hex"
67+ return
68+ }
69+
70+ len=$((16#${hex: -4}))
71+ if ((len + 2 >= total || len == 0)); then
72+ printf '%s' "$hex"
73+ return
74+ fi
75+
76+ marker=${hex:$((${#hex} - (len + 2) * 2)):2}
77+ if [[ $marker != a* ]]; then
78+ printf '%s' "$hex"
79+ return
80+ fi
81+
82+ printf '%s' "${hex:0:$((${#hex} - (len + 2) * 2))}"
83+}
84+
85+rows=()
86+unreachable=0
87+
88+printf '%-16s %-9s %-8s %-12s %s\n' CHAIN ID BYTES KIND CODEHASH
89+
90+while IFS=$'\t' read -r chain_id name weth rpc; do
91+ [[ -z ${chain_id:-} || $chain_id == \#* ]] && continue
92+
93+ code=$(cast code "$weth" --rpc-url "$rpc" 2>/dev/null)
94+
95+ if [[ ! $code == 0x* || ${#code} -le 4 ]]; then
96+ printf '%-16s %-9s %-8s %-12s %s\n' "$name" "$chain_id" "-" "-" "UNREACHABLE or no code"
97+ unreachable=$((unreachable + 1))
98+ continue
99+ fi
100+
101+ hex=${code#0x}
102+ body=$(strip_metadata "$hex")
103+
104+ codehash=$(cast keccak "0x$hex")
105+ family=$(cast keccak "0x$body")
106+
107+ kind=wrapper
108+ for selector in "${SELECTORS[@]}"; do
109+ [[ $body == *"$selector"* ]] || {
110+ kind=not-wETH
111+ break
112+ }
113+ done
114+
115+ printf '%-16s %-9s %-8s %-12s %s\n' "$name" "$chain_id" "$((${#hex} / 2))" "$kind" "$codehash"
116+ rows+=("$family|$codehash|$name|$kind|$((${#hex} / 2))")
117+done <"$CHAINS"
118+
119+echo
120+echo "families — one block per implementation, indented lines are the allowlist entries"
121+echo
122+
123+wrapper_chains=0
124+wrapper_hashes=0
125+wrapper_families=0
126+
127+for family in $(printf '%s\n' ${rows[@]+"${rows[@]}"} | cut -d'|' -f1 | sort -u); do
128+ block=$(printf '%s\n' ${rows[@]+"${rows[@]}"} | grep "^$family|")
129+ kinds=$(printf '%s\n' "$block" | cut -d'|' -f4 | sort -u | paste -sd, -)
130+ size=$(printf '%s\n' "$block" | head -1 | cut -d'|' -f5)
131+ chains=$(printf '%s\n' "$block" | wc -l | tr -d ' ')
132+
133+ printf '%s %s bytes %s chain(s) %s\n' "${family:0:18}" "$size" "$chains" "$kinds"
134+
135+ for codehash in $(printf '%s\n' "$block" | cut -d'|' -f2 | sort -u); do
136+ names=$(printf '%s\n' "$block" | grep "|$codehash|" | cut -d'|' -f3 | sort | paste -sd' ' -)
137+ printf ' %s %s\n' "$codehash" "$names"
138+
139+ if [[ $kinds == wrapper ]]; then
140+ wrapper_hashes=$((wrapper_hashes + 1))
141+ fi
142+ done
143+
144+ if [[ $kinds == wrapper ]]; then
145+ wrapper_families=$((wrapper_families + 1))
146+ wrapper_chains=$((wrapper_chains + chains))
147+ fi
148+
149+ echo
150+done
151+
152+printf '%s chains read, %s unreachable\n' "${#rows[@]}" "$unreachable"
153+printf 'wrappers: %s chains, %s families, %s exact codehashes to allowlist\n' \
154+ "$wrapper_chains" "$wrapper_families" "$wrapper_hashes"
155+echo
156+echo "A wrapper family is a candidate, not an entry — read its source before"
157+echo "adding the hash. Proxies and bridged-ETH tokens show up as not-wETH."
158+
159+[[ $unreachable -eq 0 ]] || {
160+ echo >&2
161+ echo "$unreachable row(s) unreachable — survey is incomplete" >&2
162+ exit 1
163+}