Skip to content

Commit 0cf707f

Browse files
committed
script(access): add get-allowlist.sh to reconstruct list from events
1 parent e8ccae4 commit 0cf707f

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

solidity/script/get-allowlist.sh

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env bash
2+
#
3+
# get-allowlist.sh
4+
#
5+
# Print the current set of allowed addresses on an Allowlist contract by
6+
# replaying its AddressAllowed / AddressDisallowed events.
7+
#
8+
# The Allowlist contract is intentionally not enumerable on-chain. Off-chain
9+
# consumers reconstruct the set from event logs — that's what this script does.
10+
#
11+
# Requires: cast (from foundry), jq, awk, sort.
12+
13+
set -euo pipefail
14+
15+
usage() {
16+
cat <<'EOF'
17+
Usage: get-allowlist.sh --address <allowlist> --rpc-url <url> [options]
18+
19+
Required:
20+
--address <addr> Allowlist contract address
21+
--rpc-url <url> JSON-RPC endpoint
22+
23+
Options:
24+
--from-block <n> Start block (default: 0)
25+
--to-block <n> End block (default: latest)
26+
--json Emit a JSON array (default: one address per line)
27+
-h, --help Show this help
28+
29+
Examples:
30+
get-allowlist.sh --address 0xABC... --rpc-url "$RPC_URL"
31+
get-allowlist.sh --address 0xABC... --rpc-url "$RPC_URL" --json
32+
get-allowlist.sh --address 0xABC... --rpc-url "$RPC_URL" --from-block 1000000
33+
EOF
34+
}
35+
36+
ADDRESS=""
37+
RPC_URL=""
38+
FROM_BLOCK="0"
39+
TO_BLOCK="latest"
40+
JSON_OUTPUT="false"
41+
42+
while [[ $# -gt 0 ]]; do
43+
case "$1" in
44+
--address) ADDRESS="$2"; shift 2 ;;
45+
--rpc-url) RPC_URL="$2"; shift 2 ;;
46+
--from-block) FROM_BLOCK="$2"; shift 2 ;;
47+
--to-block) TO_BLOCK="$2"; shift 2 ;;
48+
--json) JSON_OUTPUT="true"; shift ;;
49+
-h|--help) usage; exit 0 ;;
50+
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1 ;;
51+
esac
52+
done
53+
54+
if [[ -z "$ADDRESS" || -z "$RPC_URL" ]]; then
55+
echo "Error: --address and --rpc-url are required" >&2
56+
usage >&2
57+
exit 1
58+
fi
59+
60+
for cmd in cast jq awk sort; do
61+
if ! command -v "$cmd" >/dev/null 2>&1; then
62+
echo "Error: required command '$cmd' not found in PATH" >&2
63+
exit 1
64+
fi
65+
done
66+
67+
# Fetch logs for one event signature and emit tab-separated records:
68+
# <blockNumber>\t<logIndex>\t<label>\t<address>
69+
# blockNumber and logIndex may be hex (0x...) or decimal depending on cast version;
70+
# both forms are normalized later.
71+
fetch_events() {
72+
local event_sig="$1"
73+
local label="$2"
74+
cast logs \
75+
--address "$ADDRESS" \
76+
--rpc-url "$RPC_URL" \
77+
--from-block "$FROM_BLOCK" \
78+
--to-block "$TO_BLOCK" \
79+
--json \
80+
"$event_sig" \
81+
| jq -r --arg label "$label" '
82+
.[] | [
83+
.blockNumber,
84+
.logIndex,
85+
$label,
86+
("0x" + (.topics[1] | .[26:]))
87+
] | @tsv'
88+
}
89+
90+
ALLOWED_LOGS=$(fetch_events "AddressAllowed(address)" "allow")
91+
DISALLOWED_LOGS=$(fetch_events "AddressDisallowed(address)" "disallow")
92+
93+
# Two-pass awk: (1) normalize block/logIndex to zero-padded decimals so a
94+
# lexicographic sort yields chronological order; (2) replay events to compute
95+
# the current set, with the last event per address winning.
96+
RESULT=$(
97+
{ printf '%s\n' "$ALLOWED_LOGS"; printf '%s\n' "$DISALLOWED_LOGS"; } \
98+
| awk -F'\t' '
99+
function hex2dec(s, n,c,i,r) {
100+
sub(/^0x/, "", s)
101+
if (s == "") return 0
102+
r = 0
103+
for (i = 1; i <= length(s); i++) {
104+
c = tolower(substr(s, i, 1))
105+
n = (c ~ /[0-9]/) ? c + 0 : index("abcdef", c) + 9
106+
r = r * 16 + n
107+
}
108+
return r
109+
}
110+
NF == 4 {
111+
bn = $1; li = $2
112+
if (bn ~ /^0[xX]/) bn = hex2dec(bn)
113+
if (li ~ /^0[xX]/) li = hex2dec(li)
114+
printf "%020d\t%020d\t%s\t%s\n", bn, li, $3, tolower($4)
115+
}
116+
' \
117+
| sort \
118+
| awk -F'\t' '
119+
$3 == "allow" { active[$4] = 1 }
120+
$3 == "disallow" { delete active[$4] }
121+
END {
122+
n = 0
123+
for (a in active) result[n++] = a
124+
# Insertion sort — small N (admin-managed lists, not millions).
125+
for (i = 1; i < n; i++) {
126+
key = result[i]; j = i - 1
127+
while (j >= 0 && result[j] > key) {
128+
result[j+1] = result[j]; j--
129+
}
130+
result[j+1] = key
131+
}
132+
for (i = 0; i < n; i++) print result[i]
133+
}
134+
'
135+
)
136+
137+
if [[ "$JSON_OUTPUT" == "true" ]]; then
138+
if [[ -z "$RESULT" ]]; then
139+
echo "[]"
140+
else
141+
printf '%s\n' "$RESULT" | jq -R -s 'split("\n") | map(select(length > 0))'
142+
fi
143+
else
144+
if [[ -n "$RESULT" ]]; then
145+
printf '%s\n' "$RESULT"
146+
fi
147+
fi

0 commit comments

Comments
 (0)