"""Beanstalk teardown: re-derive, from the chain alone, what BIP-18 named and when that code came to exist.

Read-only JSON-RPC calls to a public archive endpoint; no keys, nothing signed or sent.
  1. BIP-18's own calldata (the propose transaction): which address it names as the init target, and the 4-byte
     function it asks that address to run.
  2. The transaction at index 12 of the attack block: the public CREATE2 deployer it called, and the address that
     deployment produces, recomputed as keccak256(0xff ++ deployer ++ salt ++ keccak256(initcode))[12:].
  3. The size of the code at BIP-18's target at the proposal block, the block before the attack, and the attack block.
Saves onchain_create2_cp0844.json beside this file.
Usage: python onchain_create2_cp0844.py [rpc_url ...]      Requires: pip install pycryptodome
"""
import datetime, json, pathlib, sys, urllib.request
from Crypto.Hash import keccak

HERE = pathlib.Path(__file__).resolve().parent
ENDPOINTS = sys.argv[1:] or ["https://ethereum-rpc.publicnode.com", "https://eth.llamarpc.com",
                             "https://eth.drpc.org", "https://1rpc.io/eth"]
TX_PROPOSE = "0x68cdec0ac76454c3b0f7af0b8a3895db00adf6daaf3b50a99716858c4fa54c6f"
TX_DEPLOY = "0x677660ce489935b94bf5ac32c494669a71ee76913ffabe623e82a7de8226b460"
TX_ATTACK = "0xcd314668aaa9bbfebaf1a0bd2b6553d01dd58899c508d4729fa7311dc5d33ad7"
TARGET = "0xe5ecf73603d98a0128f05ed30506ac7a663dbb69"


def k256(b):
    h = keccak.new(digest_bits=256)
    h.update(b)
    return h.digest()


def rpc(url, method, params):
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
    req = urllib.request.Request(url, data=body, headers={"Content-Type": "application/json",
                                                           "User-Agent": "onchain_create2_cp0844"})
    with urllib.request.urlopen(req, timeout=30) as r:
        out = json.loads(r.read().decode())
    if "error" in out:
        raise RuntimeError(out["error"])
    return out["result"]


def read(url):
    res = {"endpoint": url, "read_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")}
    # 1. BIP-18's calldata: selector, then 32-byte words
    p = rpc(url, "eth_getTransactionByHash", [TX_PROPOSE])
    words = [p["input"][10:][i:i + 64] for i in range(0, len(p["input"]) - 10, 64)]
    res["propose_selector"] = p["input"][2:10]
    res["propose_names_target"] = any(w.endswith(TARGET[2:]) for w in words)
    res["propose_carries_init_selector"] = any(w.startswith("e1c7392a") for w in words)
    res["init_selector_recomputed"] = k256(b"init()")[:4].hex()
    propose_block = int(p["blockNumber"], 16)
    # 2. the deployment one transaction ahead of the attack
    d = rpc(url, "eth_getTransactionByHash", [TX_DEPLOY])
    a = rpc(url, "eth_getTransactionByHash", [TX_ATTACK])
    rcpt = rpc(url, "eth_getTransactionReceipt", [TX_DEPLOY])
    raw = bytes.fromhex(d["input"][2:])
    salt, initcode = raw[:32], raw[32:]
    deployer = bytes.fromhex(d["to"][2:])
    addr = "0x" + k256(b"\xff" + deployer + salt + k256(initcode))[12:].hex()
    res.update({"deploy_to": d["to"], "deploy_block": int(d["blockNumber"], 16),
                "deploy_index": int(d["transactionIndex"], 16), "deploy_status": int(rcpt["status"], 16),
                "attack_block": int(a["blockNumber"], 16), "attack_index": int(a["transactionIndex"], 16),
                "same_sender": d["from"].lower() == a["from"].lower() == p["from"].lower(),
                "initcode_bytes": len(initcode), "create2_address": addr, "create2_matches_target": addr == TARGET})
    # 3. code at the target over time
    ab = res["attack_block"]
    res["target_code_bytes"] = {str(b): (len(rpc(url, "eth_getCode", [TARGET, hex(b)])) - 2) // 2
                                for b in (propose_block, ab - 1, ab)}
    return res


def main():
    last = None
    for url in ENDPOINTS:
        try:
            res = read(url)
        except Exception as e:  # noqa: BLE001 - try the next public endpoint
            last = e
            print(f"endpoint failed: {url}: {str(e)[:120]}")
            continue
        (HERE / "onchain_create2_cp0844.json").write_text(json.dumps(res, indent=1), encoding="utf-8")
        for k, v in res.items():
            print(f"{k}: {v}")
        return 0
    print(f"no endpoint answered: {last}")
    return 1


if __name__ == "__main__":
    sys.exit(main())
