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

weth-codehashes.sh · 163 lines · 5.3 KBBash Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env bash
#
# Surveys the wETH deployment on every chain in weth-chains.tsv and groups them
# by implementation, to answer the question the allowlist in
# src/WETHRegistry.sol depends on: how many distinct wETH implementations are
# actually out there?
#
# For each chain it reports three things:
#
#   codehash  keccak256 of the runtime code — exactly what EXTCODEHASH returns
#             and what the registry compares against, so these values are the
#             allowlist entries verbatim.
#   family    the same hash with solc's trailing metadata blob removed. That
#             blob is never executed and embeds a hash of the source path and
#             comments, so one implementation compiled twice gets two
#             codehashes but one family. Grouping by family is how the exact
#             hashes get reviewed together; the registry itself never strips.
#   kind      whether the code carries the ERC-20 and wrapper entrypoints
#             itself. A bridged-ETH token has no deposit/withdraw, and a proxy
#             has no entrypoints at all — its codehash commits to a forwarder,
#             not to behaviour, so it must never be allowlisted.
#
# Chains are read with eth_getCode and hashed locally rather than with
# eth_getProof, which several public RPCs do not serve.
#
# Usage: ./script/weth-codehashes.sh [chains.tsv]
#
# Exits non-zero if any row is unreachable, so a partial survey cannot be
# mistaken for a clean one.

set -uo pipefail

ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
CHAINS=${1:-$ROOT/script/weth-chains.tsv}

for cmd in cast; do
    command -v "$cmd" >/dev/null || {
        echo "$cmd not found — install foundry" >&2
        exit 1
    }
done

[[ -f $CHAINS ]] || {
    echo "no chain table at $CHAINS" >&2
    exit 1
}

# ERC-20 plus the two wrapper entrypoints. Code missing any of these is not a
# wETH this project can peg to, whatever it is called.
SELECTORS=(
    d0e30db0 # deposit()
    2e1a7d4d # withdraw(uint256)
    a9059cbb # transfer(address,uint256)
    70a08231 # balanceOf(address)
    095ea7b3 # approve(address,uint256)
    23b872dd # transferFrom(address,address,uint256)
)

# Drops solc's metadata blob: the last two bytes hold its length, and the blob
# itself starts with a CBOR map (0xa1, 0xa2, …). Anything that fails those two
# sanity checks is left whole rather than guessed at.
strip_metadata() {
    local hex=$1 len total marker
    total=$((${#hex} / 2))
    [[ ${#hex} -gt 4 ]] || {
        printf '%s' "$hex"
        return
    }

    len=$((16#${hex: -4}))
    if ((len + 2 >= total || len == 0)); then
        printf '%s' "$hex"
        return
    fi

    marker=${hex:$((${#hex} - (len + 2) * 2)):2}
    if [[ $marker != a* ]]; then
        printf '%s' "$hex"
        return
    fi

    printf '%s' "${hex:0:$((${#hex} - (len + 2) * 2))}"
}

rows=()
unreachable=0

printf '%-16s %-9s %-8s %-12s %s\n' CHAIN ID BYTES KIND CODEHASH

while IFS=$'\t' read -r chain_id name weth rpc; do
    [[ -z ${chain_id:-} || $chain_id == \#* ]] && continue

    code=$(cast code "$weth" --rpc-url "$rpc" 2>/dev/null)

    if [[ ! $code == 0x* || ${#code} -le 4 ]]; then
        printf '%-16s %-9s %-8s %-12s %s\n' "$name" "$chain_id" "-" "-" "UNREACHABLE or no code"
        unreachable=$((unreachable + 1))
        continue
    fi

    hex=${code#0x}
    body=$(strip_metadata "$hex")

    codehash=$(cast keccak "0x$hex")
    family=$(cast keccak "0x$body")

    kind=wrapper
    for selector in "${SELECTORS[@]}"; do
        [[ $body == *"$selector"* ]] || {
            kind=not-wETH
            break
        }
    done

    printf '%-16s %-9s %-8s %-12s %s\n' "$name" "$chain_id" "$((${#hex} / 2))" "$kind" "$codehash"
    rows+=("$family|$codehash|$name|$kind|$((${#hex} / 2))")
done <"$CHAINS"

echo
echo "families — one block per implementation, indented lines are the allowlist entries"
echo

wrapper_chains=0
wrapper_hashes=0
wrapper_families=0

for family in $(printf '%s\n' ${rows[@]+"${rows[@]}"} | cut -d'|' -f1 | sort -u); do
    block=$(printf '%s\n' ${rows[@]+"${rows[@]}"} | grep "^$family|")
    kinds=$(printf '%s\n' "$block" | cut -d'|' -f4 | sort -u | paste -sd, -)
    size=$(printf '%s\n' "$block" | head -1 | cut -d'|' -f5)
    chains=$(printf '%s\n' "$block" | wc -l | tr -d ' ')

    printf '%s  %s bytes  %s chain(s)  %s\n' "${family:0:18}" "$size" "$chains" "$kinds"

    for codehash in $(printf '%s\n' "$block" | cut -d'|' -f2 | sort -u); do
        names=$(printf '%s\n' "$block" | grep "|$codehash|" | cut -d'|' -f3 | sort | paste -sd' ' -)
        printf '    %s  %s\n' "$codehash" "$names"

        if [[ $kinds == wrapper ]]; then
            wrapper_hashes=$((wrapper_hashes + 1))
        fi
    done

    if [[ $kinds == wrapper ]]; then
        wrapper_families=$((wrapper_families + 1))
        wrapper_chains=$((wrapper_chains + chains))
    fi

    echo
done

printf '%s chains read, %s unreachable\n' "${#rows[@]}" "$unreachable"
printf 'wrappers: %s chains, %s families, %s exact codehashes to allowlist\n' \
    "$wrapper_chains" "$wrapper_families" "$wrapper_hashes"
echo
echo "A wrapper family is a candidate, not an entry — read its source before"
echo "adding the hash. Proxies and bridged-ETH tokens show up as not-wETH."

[[ $unreachable -eq 0 ]] || {
    echo >&2
    echo "$unreachable row(s) unreachable — survey is incomplete" >&2
    exit 1
}