122 lines
4.3 KiB
Bash
Executable File
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"
|