315 lines
12 KiB
Python
315 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import time
|
|
from decimal import Decimal, getcontext
|
|
from pathlib import Path
|
|
|
|
getcontext().prec = 50
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
DEPLOYMENT_STATUS = ROOT / "cross-chain-pmm-lps" / "config" / "deployment-status.json"
|
|
REPORT = ROOT / "reports" / "extraction" / "promod-uniswap-v2-phase1-funding-readiness-latest.json"
|
|
DOC = ROOT / "docs" / "03-deployment" / "PROMOD_UNISWAP_V2_PHASE1_FUNDING_READINESS.md"
|
|
|
|
TARGET_CHAINS = [1, 10, 25, 56, 100, 137, 8453, 42161, 42220, 43114]
|
|
RPC_ENV_KEYS = {
|
|
1: "ETHEREUM_MAINNET_RPC",
|
|
10: "OPTIMISM_MAINNET_RPC",
|
|
25: "CRONOS_RPC_URL",
|
|
56: "BSC_RPC_URL",
|
|
100: "GNOSIS_MAINNET_RPC",
|
|
137: "POLYGON_MAINNET_RPC",
|
|
8453: "BASE_MAINNET_RPC",
|
|
42161: "ARBITRUM_MAINNET_RPC",
|
|
42220: "CELO_MAINNET_RPC",
|
|
43114: "AVALANCHE_RPC_URL",
|
|
}
|
|
NETWORK_NAMES = {
|
|
1: "Ethereum Mainnet",
|
|
10: "Optimism",
|
|
25: "Cronos",
|
|
56: "BSC",
|
|
100: "Gnosis",
|
|
137: "Polygon",
|
|
8453: "Base",
|
|
42161: "Arbitrum One",
|
|
42220: "Celo",
|
|
43114: "Avalanche",
|
|
}
|
|
|
|
|
|
def now() -> str:
|
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
|
|
|
|
def write_json(path: Path, payload: dict) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2) + "\n")
|
|
|
|
|
|
def write_text(path: Path, text: str) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text.rstrip() + "\n")
|
|
|
|
|
|
def run(cmd: list[str], timeout: int = 8) -> tuple[int, str, str]:
|
|
proc = subprocess.run(cmd, text=True, capture_output=True, timeout=timeout)
|
|
return proc.returncode, proc.stdout.strip(), proc.stderr.strip()
|
|
|
|
|
|
def load_env() -> dict[str, str]:
|
|
script = (
|
|
"source smom-dbis-138/scripts/load-env.sh >/dev/null && "
|
|
"python3 - <<'PY'\n"
|
|
"import json, os\n"
|
|
"keys = [\n"
|
|
" 'PRIVATE_KEY',\n"
|
|
" 'ETHEREUM_MAINNET_RPC','OPTIMISM_MAINNET_RPC','CRONOS_RPC_URL','BSC_RPC_URL',\n"
|
|
" 'GNOSIS_MAINNET_RPC','POLYGON_MAINNET_RPC','BASE_MAINNET_RPC','ARBITRUM_MAINNET_RPC',\n"
|
|
" 'CELO_MAINNET_RPC','AVALANCHE_RPC_URL',\n"
|
|
" 'CHAIN_1_UNISWAP_V2_FACTORY','CHAIN_1_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_10_UNISWAP_V2_FACTORY','CHAIN_10_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_25_UNISWAP_V2_FACTORY','CHAIN_25_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_56_UNISWAP_V2_FACTORY','CHAIN_56_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_100_UNISWAP_V2_FACTORY','CHAIN_100_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_137_UNISWAP_V2_FACTORY','CHAIN_137_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_8453_UNISWAP_V2_FACTORY','CHAIN_8453_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_42161_UNISWAP_V2_FACTORY','CHAIN_42161_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_42220_UNISWAP_V2_FACTORY','CHAIN_42220_UNISWAP_V2_ROUTER',\n"
|
|
" 'CHAIN_43114_UNISWAP_V2_FACTORY','CHAIN_43114_UNISWAP_V2_ROUTER'\n"
|
|
"]\n"
|
|
"print(json.dumps({k: os.environ.get(k, '') for k in keys}))\n"
|
|
"PY"
|
|
)
|
|
rc, out, err = run(["bash", "-lc", script], timeout=10)
|
|
if rc != 0:
|
|
raise RuntimeError(f"failed to load env: {err or out}")
|
|
return json.loads(out)
|
|
|
|
|
|
def signer_address(private_key: str) -> str:
|
|
rc, out, err = run(["cast", "wallet", "address", "--private-key", private_key], timeout=10)
|
|
if rc != 0:
|
|
raise RuntimeError(f"failed to derive signer: {err or out}")
|
|
return out
|
|
|
|
|
|
def chain_entry(status: dict, chain_id: int) -> dict:
|
|
return status["chains"].get(str(chain_id)) or status["chains"].get(chain_id) or {}
|
|
|
|
|
|
def token_address(status: dict, chain_id: int, symbol: str) -> str:
|
|
value = chain_entry(status, chain_id).get("cwTokens", {}).get(symbol)
|
|
if isinstance(value, dict):
|
|
return value.get("address") or value.get("token") or ""
|
|
return value or ""
|
|
|
|
|
|
def token_decimals(rpc_url: str, token: str) -> int:
|
|
rc, out, _ = run(["cast", "call", token, "decimals()(uint8)", "--rpc-url", rpc_url], timeout=5)
|
|
return int(out) if rc == 0 and out else 18
|
|
|
|
|
|
def token_value_human(raw: str, decimals: int) -> str:
|
|
raw_int = int(raw.split()[0], 0)
|
|
return format(Decimal(raw_int) / (Decimal(10) ** decimals), "f")
|
|
|
|
|
|
def min_human(a: str, b: str) -> str:
|
|
da = Decimal(a)
|
|
db = Decimal(b)
|
|
return format(da if da <= db else db, "f")
|
|
|
|
|
|
def pair_reserves(rpc_url: str, pair: str) -> tuple[str, str]:
|
|
if pair in ("", "0x0000000000000000000000000000000000000000"):
|
|
return "0", "0"
|
|
rc, out, _ = run(["cast", "call", pair, "getReserves()(uint112,uint112,uint32)", "--rpc-url", rpc_url], timeout=5)
|
|
if rc != 0 or not out:
|
|
return "0", "0"
|
|
parts = out.split()
|
|
if len(parts) < 2:
|
|
return "0", "0"
|
|
return parts[0], parts[1]
|
|
|
|
|
|
def main() -> None:
|
|
env = load_env()
|
|
status = json.loads(DEPLOYMENT_STATUS.read_text())
|
|
signer = signer_address(env["PRIVATE_KEY"])
|
|
|
|
entries: list[dict] = []
|
|
completed = []
|
|
ready_now = []
|
|
needs_funding = []
|
|
|
|
for chain_id in TARGET_CHAINS:
|
|
rpc_key = RPC_ENV_KEYS[chain_id]
|
|
rpc_url = env.get(rpc_key, "")
|
|
factory = env.get(f"CHAIN_{chain_id}_UNISWAP_V2_FACTORY", "")
|
|
router = env.get(f"CHAIN_{chain_id}_UNISWAP_V2_ROUTER", "")
|
|
cwusdt = token_address(status, chain_id, "cWUSDT")
|
|
cwusdc = token_address(status, chain_id, "cWUSDC")
|
|
|
|
pair_rc, pair_out, _ = run(
|
|
["cast", "call", factory, "getPair(address,address)(address)", cwusdt, cwusdc, "--rpc-url", rpc_url],
|
|
timeout=5,
|
|
)
|
|
gas_rc, gas_out, _ = run(["cast", "balance", signer, "--ether", "--rpc-url", rpc_url], timeout=5)
|
|
|
|
cwusdt_decimals = token_decimals(rpc_url, cwusdt)
|
|
cwusdc_decimals = token_decimals(rpc_url, cwusdc)
|
|
|
|
b1_rc, b1_out, _ = run(
|
|
["cast", "call", cwusdt, "balanceOf(address)(uint256)", signer, "--rpc-url", rpc_url],
|
|
timeout=5,
|
|
)
|
|
b2_rc, b2_out, _ = run(
|
|
["cast", "call", cwusdc, "balanceOf(address)(uint256)", signer, "--rpc-url", rpc_url],
|
|
timeout=5,
|
|
)
|
|
a1_rc, a1_out, _ = run(
|
|
["cast", "call", cwusdt, "allowance(address,address)(uint256)", signer, router, "--rpc-url", rpc_url],
|
|
timeout=5,
|
|
)
|
|
a2_rc, a2_out, _ = run(
|
|
["cast", "call", cwusdc, "allowance(address,address)(uint256)", signer, router, "--rpc-url", rpc_url],
|
|
timeout=5,
|
|
)
|
|
|
|
cwusdt_raw = b1_out.split()[0] if b1_rc == 0 and b1_out else "0"
|
|
cwusdc_raw = b2_out.split()[0] if b2_rc == 0 and b2_out else "0"
|
|
cwusdt_human = token_value_human(cwusdt_raw, cwusdt_decimals)
|
|
cwusdc_human = token_value_human(cwusdc_raw, cwusdc_decimals)
|
|
max_seed = min_human(cwusdt_human, cwusdc_human)
|
|
gas_human = gas_out if gas_rc == 0 else "0"
|
|
pair_address = pair_out if pair_rc == 0 else ""
|
|
reserve0_raw, reserve1_raw = pair_reserves(rpc_url, pair_address)
|
|
pair_seeded_live = pair_address not in ("", "0x0000000000000000000000000000000000000000") and (
|
|
int(reserve0_raw, 0) > 0 or int(reserve1_raw, 0) > 0
|
|
)
|
|
|
|
has_both_sides = Decimal(cwusdt_human) > 0 and Decimal(cwusdc_human) > 0
|
|
has_gas = Decimal(gas_human) > Decimal("0.001")
|
|
funding_ready = has_both_sides and has_gas and pair_address == "0x0000000000000000000000000000000000000000"
|
|
|
|
entry = {
|
|
"chain_id": chain_id,
|
|
"network": NETWORK_NAMES[chain_id],
|
|
"phase_1_pair": "cWUSDT/cWUSDC",
|
|
"signer": signer,
|
|
"rpc_env_key": rpc_key,
|
|
"factory": factory,
|
|
"router": router,
|
|
"pair_address": pair_address,
|
|
"pair_exists": pair_address not in ("", "0x0000000000000000000000000000000000000000"),
|
|
"pair_seeded_live": pair_seeded_live,
|
|
"native_balance": gas_human,
|
|
"native_balance_ok_for_ops": has_gas,
|
|
"cwusdt": {
|
|
"address": cwusdt,
|
|
"decimals": cwusdt_decimals,
|
|
"balance_raw": cwusdt_raw,
|
|
"balance_human": cwusdt_human,
|
|
"allowance_raw": a1_out.split()[0] if a1_rc == 0 and a1_out else "0",
|
|
},
|
|
"cwusdc": {
|
|
"address": cwusdc,
|
|
"decimals": cwusdc_decimals,
|
|
"balance_raw": cwusdc_raw,
|
|
"balance_human": cwusdc_human,
|
|
"allowance_raw": a2_out.split()[0] if a2_rc == 0 and a2_out else "0",
|
|
},
|
|
"max_equal_seed_human": max_seed,
|
|
"reserve0_raw": reserve0_raw,
|
|
"reserve1_raw": reserve1_raw,
|
|
"execution_status": "completed" if pair_seeded_live else ("ready_now" if funding_ready else "needs_funding"),
|
|
"funding_ready_now": funding_ready,
|
|
"funding_blockers": [],
|
|
}
|
|
|
|
if not has_gas:
|
|
entry["funding_blockers"].append("native gas below 0.001")
|
|
if Decimal(cwusdt_human) <= 0:
|
|
entry["funding_blockers"].append("cWUSDT balance is zero")
|
|
if Decimal(cwusdc_human) <= 0:
|
|
entry["funding_blockers"].append("cWUSDC balance is zero")
|
|
if entry["pair_exists"] and not pair_seeded_live:
|
|
entry["funding_blockers"].append("pair exists but is not yet seeded")
|
|
|
|
if pair_seeded_live:
|
|
completed.append(chain_id)
|
|
elif funding_ready:
|
|
ready_now.append(chain_id)
|
|
else:
|
|
needs_funding.append(chain_id)
|
|
|
|
entries.append(entry)
|
|
|
|
payload = {
|
|
"generated_at": now(),
|
|
"program_name": "Mr. Promod Uniswap V2 phase 1 funding readiness",
|
|
"purpose": "Live deployer-wallet funding view for seeding cWUSDT/cWUSDC phase-1 pools chain by chain.",
|
|
"signer": signer,
|
|
"phase_1_pair": "cWUSDT/cWUSDC",
|
|
"completed_chain_ids": completed,
|
|
"ready_now_chain_ids": ready_now,
|
|
"needs_funding_chain_ids": needs_funding,
|
|
"entries": entries,
|
|
"source_artifacts": [
|
|
"cross-chain-pmm-lps/config/deployment-status.json",
|
|
"smom-dbis-138/.env",
|
|
"smom-dbis-138/scripts/load-env.sh",
|
|
],
|
|
}
|
|
write_json(REPORT, payload)
|
|
|
|
lines = [
|
|
"# Mr. Promod Uniswap V2 Phase 1 Funding Readiness",
|
|
"",
|
|
f"- Generated: `{payload['generated_at']}`",
|
|
f"- Signer: `{signer}`",
|
|
"- Purpose: live deployer-wallet funding view for seeding `cWUSDT/cWUSDC` phase-1 pools chain by chain.",
|
|
f"- Completed: {', '.join(f'`{cid}`' for cid in completed) if completed else 'none'}",
|
|
f"- Ready now: {', '.join(f'`{cid}`' for cid in ready_now) if ready_now else 'none'}",
|
|
f"- Needs funding: {', '.join(f'`{cid}`' for cid in needs_funding) if needs_funding else 'none'}",
|
|
"",
|
|
"| Chain | Network | Status | Pair Exists | Seeded Live | Native Gas | cWUSDT | cWUSDC | Max Equal Seed |",
|
|
"|---|---|---|---|---|---:|---:|---:|---:|",
|
|
]
|
|
|
|
for entry in entries:
|
|
lines.append(
|
|
f"| `{entry['chain_id']}` | {entry['network']} | "
|
|
f"`{entry['execution_status']}` | "
|
|
f"`{str(entry['pair_exists']).lower()}` | "
|
|
f"`{str(entry['pair_seeded_live']).lower()}` | "
|
|
f"`{entry['native_balance']}` | "
|
|
f"`{entry['cwusdt']['balance_human']}` | "
|
|
f"`{entry['cwusdc']['balance_human']}` | "
|
|
f"`{entry['max_equal_seed_human']}` |"
|
|
)
|
|
|
|
lines.extend(["", "## Blockers", ""])
|
|
for entry in entries:
|
|
lines.append(f"### Chain `{entry['chain_id']}` — {entry['network']}")
|
|
lines.append("")
|
|
lines.append(f"- execution status: `{entry['execution_status']}`")
|
|
lines.append(f"- pair seeded live: `{str(entry['pair_seeded_live']).lower()}`")
|
|
if entry["funding_blockers"]:
|
|
for blocker in entry["funding_blockers"]:
|
|
lines.append(f"- {blocker}")
|
|
else:
|
|
lines.append("- no funding blockers")
|
|
lines.append("")
|
|
|
|
write_text(DOC, "\n".join(lines))
|
|
print(REPORT)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|