Add conviction voting data and Dune pool balance exports
Conviction Voting (from Gnosis Chain RPC, contract 0xca164930...): - 47 proposals, 36 funded, 10 cancelled, 680,295 TEC disbursed - 891 stake events from 159 unique stakers - 873 conviction support update events - Notable: TE Academy (64K), Gravity DAO (76K), cadCAD (54K) Dune exports (4 queries): - Pool balances (4,220 rows), common pool (872), reserve pool (887) - Token balances with USD valuations (1,986 rows) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6496f46430
commit
041ee79945
|
|
@ -0,0 +1,209 @@
|
|||
"""Decode conviction voting events from raw RPC log data."""
|
||||
import json
|
||||
import csv
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
DATA_DIR = sys.argv[1] if len(sys.argv) > 1 else "data/onchain"
|
||||
|
||||
with open("/tmp/cv_events.json") as f:
|
||||
d = json.load(f)
|
||||
logs = d.get("result", [])
|
||||
|
||||
PROPOSAL_ADDED = "0xe180363919da754b2737a8f10869b7d2df0be7ef0e81339d3b5dabba166060ed"
|
||||
STAKE_CHANGED = "0x28d9b583e0c477691a08f6c1e00fedc0895ed4221487c627fa96a7024119f499"
|
||||
SUPPORT_CHANGED = "0x16f23283da3097bc9027dcdf31f24863b1520556f04818d406f0e6ecd08580f5"
|
||||
PROPOSAL_EXECUTED = "0xf758fc91e01b00ea6b4a6138756f7f28e021f9bf21db6dbf8c36c88eb737257a"
|
||||
|
||||
by_topic = defaultdict(list)
|
||||
for l in logs:
|
||||
by_topic[l["topics"][0]].append(l)
|
||||
|
||||
# --- Decode Proposals ---
|
||||
proposals = {}
|
||||
for l in by_topic[PROPOSAL_ADDED]:
|
||||
topics = l["topics"]
|
||||
prop_id = int(topics[2], 16)
|
||||
creator_int = int(topics[1], 16)
|
||||
creator = "0x" + hex(creator_int)[2:].zfill(40) if creator_int > 0 else "0x0"
|
||||
action_id = int(topics[3], 16)
|
||||
block = int(l["blockNumber"], 16)
|
||||
|
||||
data = l["data"][2:]
|
||||
chunks = [data[i : i + 64] for i in range(0, len(data), 64)]
|
||||
|
||||
amount = int(chunks[2], 16) / 1e18 if len(chunks) > 2 else 0
|
||||
stable = bool(int(chunks[3], 16)) if len(chunks) > 3 else False
|
||||
beneficiary_raw = chunks[4] if len(chunks) > 4 else ""
|
||||
beneficiary = "0x" + beneficiary_raw[24:] if beneficiary_raw else ""
|
||||
|
||||
# Extract link/metadata
|
||||
link = ""
|
||||
if len(chunks) > 6:
|
||||
link_len = int(chunks[5], 16)
|
||||
if 0 < link_len < 200:
|
||||
link_hex = "".join(chunks[6 : 6 + (link_len + 31) // 32])
|
||||
try:
|
||||
link = bytes.fromhex(link_hex[: link_len * 2]).decode(
|
||||
"utf-8", errors="replace"
|
||||
)
|
||||
except Exception:
|
||||
link = ""
|
||||
|
||||
proposals[prop_id] = {
|
||||
"id": prop_id,
|
||||
"block": block,
|
||||
"tx_hash": l["transactionHash"],
|
||||
"creator": creator,
|
||||
"amount_requested": amount,
|
||||
"stable": stable,
|
||||
"beneficiary": beneficiary,
|
||||
"action_id": action_id,
|
||||
"link": link.strip("\x00"),
|
||||
"status": "open",
|
||||
"executed_block": "",
|
||||
}
|
||||
|
||||
# --- Executed proposals ---
|
||||
for l in by_topic.get(PROPOSAL_EXECUTED, []):
|
||||
prop_id = int(l["topics"][1], 16)
|
||||
block = int(l["blockNumber"], 16)
|
||||
if prop_id in proposals:
|
||||
proposals[prop_id]["status"] = "executed"
|
||||
proposals[prop_id]["executed_block"] = block
|
||||
|
||||
# --- Check other event types for cancellation ---
|
||||
other_topics = set(by_topic.keys()) - {
|
||||
PROPOSAL_ADDED,
|
||||
STAKE_CHANGED,
|
||||
SUPPORT_CHANGED,
|
||||
PROPOSAL_EXECUTED,
|
||||
}
|
||||
for t in other_topics:
|
||||
for l in by_topic[t]:
|
||||
if len(l["topics"]) > 1:
|
||||
try:
|
||||
prop_id = int(l["topics"][1], 16)
|
||||
if prop_id in proposals and proposals[prop_id]["status"] == "open":
|
||||
proposals[prop_id]["status"] = "cancelled"
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# --- Stakes ---
|
||||
stakes = []
|
||||
for l in by_topic[STAKE_CHANGED]:
|
||||
staker = "0x" + l["topics"][1][26:]
|
||||
prop_id = int(l["topics"][2], 16) if len(l["topics"]) > 2 else -1
|
||||
block = int(l["blockNumber"], 16)
|
||||
data = l["data"][2:]
|
||||
chunks = [data[i : i + 64] for i in range(0, min(len(data), 320), 64)]
|
||||
|
||||
tokens_staked = int(chunks[0], 16) / 1e18 if chunks else 0
|
||||
total_staked = int(chunks[1], 16) / 1e18 if len(chunks) > 1 else 0
|
||||
conviction = int(chunks[2], 16) / 1e18 if len(chunks) > 2 else 0
|
||||
|
||||
stakes.append(
|
||||
{
|
||||
"block": block,
|
||||
"tx_hash": l["transactionHash"],
|
||||
"staker": staker,
|
||||
"proposal_id": prop_id,
|
||||
"tokens_staked": tokens_staked,
|
||||
"total_tokens_staked": total_staked,
|
||||
"conviction": conviction,
|
||||
}
|
||||
)
|
||||
|
||||
# --- Support updates ---
|
||||
supports = []
|
||||
for l in by_topic[SUPPORT_CHANGED]:
|
||||
prop_id = int(l["topics"][1], 16)
|
||||
block = int(l["blockNumber"], 16)
|
||||
supports.append(
|
||||
{
|
||||
"block": block,
|
||||
"proposal_id": prop_id,
|
||||
"tx_hash": l["transactionHash"],
|
||||
}
|
||||
)
|
||||
|
||||
# Print summary
|
||||
props_list = sorted(proposals.values(), key=lambda x: x["id"])
|
||||
print("=== CONVICTION VOTING SUMMARY ===")
|
||||
print(f"Total proposals: {len(props_list)}")
|
||||
print(
|
||||
f" Executed: {sum(1 for p in props_list if p['status']=='executed')}"
|
||||
)
|
||||
print(
|
||||
f" Cancelled: {sum(1 for p in props_list if p['status']=='cancelled')}"
|
||||
)
|
||||
print(f" Open: {sum(1 for p in props_list if p['status']=='open')}")
|
||||
print(f"Stake events: {len(stakes)}")
|
||||
print(f"Support updates: {len(supports)}")
|
||||
print(f"Unique stakers: {len(set(s['staker'] for s in stakes))}")
|
||||
total_requested = sum(p["amount_requested"] for p in props_list)
|
||||
total_funded = sum(
|
||||
p["amount_requested"] for p in props_list if p["status"] == "executed"
|
||||
)
|
||||
print(f"Total requested: {total_requested:,.0f} tokens")
|
||||
print(f"Total funded (executed): {total_funded:,.0f} tokens")
|
||||
|
||||
print(f"\n=== ALL PROPOSALS ===")
|
||||
for p in props_list:
|
||||
if p["status"] == "executed":
|
||||
marker = "FUNDED"
|
||||
elif p["status"] == "cancelled":
|
||||
marker = "CANCEL"
|
||||
else:
|
||||
marker = "OPEN "
|
||||
print(
|
||||
f' {marker} #{p["id"]:2d} | {p["amount_requested"]:>10,.0f} tokens'
|
||||
f' | beneficiary: {p["beneficiary"][:16]}...'
|
||||
f' | link: {p["link"][:60]}'
|
||||
)
|
||||
|
||||
# Save CSVs
|
||||
fields_p = [
|
||||
"id",
|
||||
"block",
|
||||
"tx_hash",
|
||||
"creator",
|
||||
"amount_requested",
|
||||
"stable",
|
||||
"beneficiary",
|
||||
"action_id",
|
||||
"link",
|
||||
"status",
|
||||
"executed_block",
|
||||
]
|
||||
with open(f"{DATA_DIR}/cv_proposals.csv", "w", newline="") as f:
|
||||
w = csv.DictWriter(f, fieldnames=fields_p, extrasaction="ignore")
|
||||
w.writeheader()
|
||||
for p in props_list:
|
||||
w.writerow(p)
|
||||
|
||||
fields_s = [
|
||||
"block",
|
||||
"tx_hash",
|
||||
"staker",
|
||||
"proposal_id",
|
||||
"tokens_staked",
|
||||
"total_tokens_staked",
|
||||
"conviction",
|
||||
]
|
||||
with open(f"{DATA_DIR}/cv_stakes.csv", "w", newline="") as f:
|
||||
w = csv.DictWriter(f, fieldnames=fields_s)
|
||||
w.writeheader()
|
||||
for s in stakes:
|
||||
w.writerow(s)
|
||||
|
||||
fields_u = ["block", "proposal_id", "tx_hash"]
|
||||
with open(f"{DATA_DIR}/cv_support_updates.csv", "w", newline="") as f:
|
||||
w = csv.DictWriter(f, fieldnames=fields_u)
|
||||
w.writeheader()
|
||||
for s in supports:
|
||||
w.writerow(s)
|
||||
|
||||
print(f"\nSaved: cv_proposals.csv ({len(props_list)} rows)")
|
||||
print(f"Saved: cv_stakes.csv ({len(stakes)} rows)")
|
||||
print(f"Saved: cv_support_updates.csv ({len(supports)} rows)")
|
||||
Loading…
Reference in New Issue