Files
proxmox/scripts/verify/decode-singleton-deploy-pending-2103.sh
2026-04-24 10:55:55 -07:00

122 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# List pending txs on 2103 that target the EIP-2470 singleton and decode deploy(bytes) initcode.
# Usage: ./scripts/verify/decode-singleton-deploy-pending-2103.sh
# Optional: RPC_URL_2103=... PENDING_TX_FROM=0xB2dE... (stuck tx signer; avoids operator DEPLOYER from dotenv) SINGLETON=0x4e59...
set -euo pipefail
RPC_URL="${RPC_URL_2103:-http://192.168.11.217:8545}"
PENDING_TX_FROM="${PENDING_TX_FROM:-0xB2dEA0e264ddfFf91057A3415112e57A1a5Eac14}"
SINGLETON="${SINGLETON:-0x4e59b44847b379578588920cA78FbF26c0B4956C}"
OUT_DIR="${OUT_DIR:-/tmp/singleton-decodes-$(date +%Y%m%d%H%M%S)}"
export RPC_URL PENDING_TX_FROM SINGLETON OUT_DIR
DEPLOYER="${PENDING_TX_FROM}"
export DEPLOYER
mkdir -p "$OUT_DIR"
echo "RPC: $RPC_URL pending from filter: $PENDING_TX_FROM singleton: $SINGLETON"
echo "Output: $OUT_DIR"
echo ""
python3 <<'PY'
import json, sys, subprocess, os, urllib.request
rpc = os.environ["RPC_URL"]
dep = os.environ["DEPLOYER"].lower()
sing = os.environ["SINGLETON"].lower()
out = os.environ["OUT_DIR"]
sel_4 = bytes.fromhex("00774360")
req = urllib.request.Request(
rpc,
data=json.dumps({"jsonrpc": "2.0", "method": "txpool_besuPendingTransactions", "params": [], "id": 1}).encode(),
headers={"Content-Type": "application/json"},
)
raw = urllib.request.urlopen(req, timeout=15).read()
d = json.loads(raw.decode())
if d.get("error"):
print("RPC error:", d["error"])
sys.exit(1)
txs = d.get("result") or []
print("Total pending on 2103:", len(txs))
matched = []
for t in txs:
to = (t.get("to") or "").lower()
fr = (t.get("from") or "").lower()
if to != sing:
continue
if fr != dep:
continue
matched.append(t)
print("Matching deployer -> singleton:", len(matched))
if not matched:
print("Nothing to decode. Re-run when those txs are in 2103's pool, or paste a raw tx into this script.")
sys.exit(0)
for i, t in enumerate(matched):
h = t.get("hash", "unknown")
raw_hex = t.get("input") or "0x"
if not raw_hex.startswith("0x"):
raw_hex = "0x" + raw_hex
inp = raw_hex[2:]
b = bytes.fromhex(inp)
j = b.find(sel_4)
if j >= 0 and j != 0:
print("INFO", h, "deploy(bytes) selector 0x00774360 at byte offset", j, "(not at 0; Thirdweb / padding)")
b = b[j:]
if len(b) < 4 or b[:4] != sel_4:
p = os.path.join(out, f"full_calldata_{i+1}_{h[:10]}.hex")
with open(p, "w") as f:
f.write(raw_hex.rstrip() + "\n")
if j < 0:
print("WARN", h, "no 0x00774360 in this `input` — on-chain `eth_call` may still use full RPC `input` (see dry-run-thirdweb-singleton-2103.sh) ->", p)
else:
print("WARN", h, "not deploy(bytes) 0x00774360; saved for replay ->", p)
continue
if len(b) < 4 + 32 + 32:
print("Input too short for ABI deploy(bytes)", h, "len", len(b))
continue
off = int.from_bytes(b[4:36], "big")
if 4 + off + 32 > len(b):
print("Bad offset", off, h)
continue
ln = int.from_bytes(b[4 + off : 4 + off + 32], "big")
init = b[4 + off + 32 : 4 + off + 32 + ln]
if len(init) != ln:
print("Length mismatch", h, ln, len(init))
continue
path = os.path.join(out, f"initcode_{i+1}_{h[:10]}.hex")
with open(path, "w") as f:
f.write("0x" + init.hex())
print("---", h, "---")
print(" nonce:", t.get("nonce"), " gas:", t.get("gas"), " initcode bytes:", len(init), "->", path)
try:
r = subprocess.run(
["cast", "keccak", "0x" + init.hex()],
capture_output=True, text=True, timeout=5, check=False,
)
if r.returncode == 0:
print(" keccak256(initcode):", r.stdout.strip())
except FileNotFoundError:
pass
# After slice, b is body starting at 0x00774360; full tx `input` for the node is still raw_hex
bl = len(b)
print(" Body from selector (for ABI decode only):", bl, "bytes")
def _parse_gas(x):
if isinstance(x, int):
return x
s = str(x).strip()
if s.lower().startswith("0x"):
return int(s, 16)
return int(s, 10)
g = t.get("gas", "0x500000")
try:
gl = _parse_gas(g)
except (ValueError, TypeError):
gl = 5242880
print(" Example: cast send -r", rpc, "--private-key <KEY> --gas-limit", gl, sing, raw_hex)
print()
PY
echo "Done. Files under $OUT_DIR"