Skip to content

Commit 5ddd2b6

Browse files
committed
last developments
1 parent 2d3754b commit 5ddd2b6

10 files changed

Lines changed: 48975 additions & 176309 deletions

scripts/study/analyze_yb/all_users_check.py

Lines changed: 141 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22
import json
3+
import csv
34
from dataclasses import dataclass
4-
from typing import Dict, List, Tuple
5+
from typing import Dict, List, Tuple, Union
56

67
from web3 import Web3
78
from web3.types import BlockIdentifier
@@ -10,23 +11,33 @@
1011
from abis import lt_abi # local file in this folder
1112
from decimal import Decimal, getcontext
1213

13-
# increase decimal precision for exact scaling and sums
14+
# Increase decimal precision for exact scaling and sums
1415
getcontext().prec = 60
1516

1617

1718
# --------------------------- Configuration ---------------------------------
1819

19-
# LT vaults per pool name
20+
# LT vaults per pool name (same as in all_users_check.py)
2021
LT_POOLS: Dict[str, str] = {
2122
"yb_cbBTC": "0xD6a1147666f6E4d7161caf436d9923D44d901112",
2223
"yb_wBTC": "0x6095a220C5567360d459462A25b1AD5aEAD45204",
2324
"yb_tBTC": "0x2B513eBe7070Cff91cf699a0BFe5075020C732FF",
2425
}
2526

27+
# Token decimals per LT pool
28+
DECIMALS: Dict[str, int] = {
29+
"yb_cbBTC": 8,
30+
"yb_wBTC": 8,
31+
"yb_tBTC": 18,
32+
}
33+
34+
# Default snapshot block (override with env LT_SNAPSHOT_BLOCK)
35+
SNAPSHOT_BLOCK = int(os.environ.get("LT_SNAPSHOT_BLOCK", 23550167)) # 22pm utc
36+
2637
# Default scan start block (override with env LT_START_BLOCK or LT_START_BLOCK_<POOL>)
2738
DEFAULT_START_BLOCK = int(os.environ.get("LT_START_BLOCK", 23_434_000))
2839

29-
# Multicall options (mirrors fetch_data_events.py style, simplified)
40+
# Multicall options (mirrors style in other scripts)
3041
MULTICALL_OPTIONS = {
3142
"provider_url": os.environ.get("WEB3_PROVIDER_URL", ""),
3243
"batch": 100,
@@ -66,14 +77,6 @@ def get_pool_start_block(pool_name: str) -> int:
6677
return int(os.environ.get(env_key, DEFAULT_START_BLOCK))
6778

6879

69-
# Token decimals per LT pool
70-
DECIMALS: Dict[str, int] = {
71-
"yb_cbBTC": 8,
72-
"yb_wBTC": 8,
73-
"yb_tBTC": 18,
74-
}
75-
76-
7780
def scale_amount(raw: int, decimals: int) -> Decimal:
7881
# high precision division
7982
return Decimal(raw) / (Decimal(10) ** decimals)
@@ -97,18 +100,21 @@ def gather_user_aggregates(
97100
start_block: int,
98101
to_block: BlockIdentifier,
99102
) -> Tuple[Dict[str, UserAgg], int]:
100-
"""Return mapping owner->UserAgg and latest scanned block (int)."""
103+
"""Return mapping owner->UserAgg and latest scanned (int) end block.
104+
105+
Scans Deposit and Withdraw events from start_block..to_block (inclusive).
106+
"""
101107
contract = w3.eth.contract(address=checksum(pool_addr), abi=json.loads(lt_abi))
102108

103-
latest_block = w3.eth.get_block("latest")["number"] if to_block == "latest" else int(to_block) # type: ignore[arg-type]
109+
end_block = w3.eth.get_block("latest")["number"] if to_block == "latest" else int(to_block)
104110
start_block = int(start_block)
105111
decimals = DECIMALS.get(pool_name, 18)
106112

107113
dep_logs = fetch_event_logs_chunked(
108-
contract.events.Deposit(), start_block, latest_block, LOG_CHUNK
114+
contract.events.Deposit(), start_block, end_block, LOG_CHUNK
109115
)
110116
wd_logs = fetch_event_logs_chunked(
111-
contract.events.Withdraw(), start_block, latest_block, LOG_CHUNK
117+
contract.events.Withdraw(), start_block, end_block, LOG_CHUNK
112118
)
113119

114120
aggs: Dict[str, UserAgg] = {}
@@ -129,18 +135,29 @@ def gather_user_aggregates(
129135
a.withdraws_assets_norm += scale_amount(int(ev["args"].get("assets", 0)), decimals)
130136
a.shares_out += int(ev["args"].get("shares", 0))
131137

132-
return aggs, latest_block
138+
return aggs, end_block
133139

134140

135-
def preview_withdraw_many(
136-
w3: Web3, pool_addr: str, shares_by_owner: Dict[str, int]
141+
def preview_withdraw_many_at(
142+
w3: Web3,
143+
pool_addr: str,
144+
shares_by_owner: Dict[str, int],
145+
block_id: Union[int, str],
137146
) -> Dict[str, int]:
138-
"""Use multicall to preview_withdraw for each owner with net shares > 0."""
147+
"""Use multicall to preview_withdraw for each owner at a specific block.
148+
149+
Returns: mapping owner -> preview_withdraw result (raw int asset amount).
150+
"""
151+
if not shares_by_owner:
152+
return {}
153+
139154
contract = w3.eth.contract(address=checksum(pool_addr), abi=json.loads(lt_abi))
140155
multicall = Multicall(**MULTICALL_OPTIONS)
141156

142157
calls, addresses, owners = [], [], []
143158
for owner, shares in shares_by_owner.items():
159+
if int(shares) <= 0:
160+
continue
144161
calls.append(contract.functions.preview_withdraw(int(shares)))
145162
addresses.append(contract.address)
146163
owners.append(owner)
@@ -149,12 +166,11 @@ def preview_withdraw_many(
149166
return {}
150167

151168
results = multicall.aggregate(
152-
calls, use_try=True, addresses=addresses, block_identifier="latest"
169+
calls, use_try=True, addresses=addresses, block_identifier=block_id
153170
)
154171

155172
out: Dict[str, int] = {}
156173
for owner, value in zip(owners, results):
157-
# value may be None if call reverted; treat as 0
158174
try:
159175
out[owner] = int(value) if value is not None else 0
160176
except Exception:
@@ -167,96 +183,131 @@ def main():
167183
abi = json.loads(lt_abi)
168184
print(f"Loaded LT ABI with events: {[e['name'] for e in abi if e.get('type')=='event']}")
169185

170-
all_rows: List[Dict] = []
186+
snapshot_block = SNAPSHOT_BLOCK
187+
print(f"Snapshot block: {snapshot_block}")
188+
189+
rows: List[Dict[str, str]] = []
171190

172191
for pool_name, addr in LT_POOLS.items():
173192
start_block = get_pool_start_block(pool_name)
174-
print(f"\nPool {pool_name} @ {checksum(addr)} | scanning from block {start_block} ...")
193+
print(f"\nPool {pool_name} @ {checksum(addr)} | scanning from block {start_block}")
194+
195+
# 1) Aggregates up to snapshot
196+
aggs_then, end_then = gather_user_aggregates(
197+
w3, pool_name, addr, start_block, to_block=snapshot_block
198+
)
199+
owners_then = {
200+
owner: agg.net_shares() for owner, agg in aggs_then.items() if agg.net_shares() > 0
201+
}
202+
print(f" Snapshot owners with shares > 0 at {snapshot_block}: {len(owners_then)}")
203+
204+
# 2) Aggregates up to latest
205+
aggs_now, end_now = gather_user_aggregates(
206+
w3, pool_name, addr, start_block, to_block="latest"
207+
)
208+
owners_now = {
209+
owner: agg.net_shares() for owner, agg in aggs_now.items() if agg.net_shares() > 0
210+
}
211+
print(f" Latest owners with shares > 0 at {end_now}: {len(owners_now)}")
175212

176-
aggs, latest = gather_user_aggregates(w3, pool_name, addr, start_block, to_block="latest")
177-
print(f" Found {len(aggs)} unique owners with activity (latest block {latest}).")
213+
# 3) Preview withdraw amounts at snapshot for snapshot owners
214+
preview_then_raw = preview_withdraw_many_at(w3, addr, owners_then, block_id=snapshot_block)
178215

179-
shares_by_owner = {owner: a.net_shares() for owner, a in aggs.items() if a.net_shares() > 0}
180-
print(f" Previewing withdraw for {len(shares_by_owner)} owners with net shares > 0 ...")
216+
# 4) Preview withdraw amounts now for the SAME owner set, using current shares
217+
owners_now_subset = {owner: owners_now.get(owner, 0) for owner in owners_then.keys()}
218+
preview_now_raw = preview_withdraw_many_at(w3, addr, owners_now_subset, block_id="latest")
181219

182-
est_assets_now = preview_withdraw_many(w3, addr, shares_by_owner)
183220
decimals = DECIMALS.get(pool_name, 18)
184221
factor = Decimal(10) ** decimals
185222

186-
# Build rows
187-
for owner, agg in aggs.items():
188-
net_shares = agg.net_shares()
189-
unrealized_assets_norm = (
190-
Decimal(int(est_assets_now.get(owner, 0))) / factor
191-
if net_shares > 0
223+
# 5) Build rows per snapshot owner with requested metrics
224+
for owner in owners_then.keys():
225+
agg_now = aggs_now.get(owner, UserAgg())
226+
assets_then = int(preview_then_raw.get(owner, 0))
227+
assets_now = int(preview_now_raw.get(owner, 0))
228+
229+
then_norm = Decimal(assets_then) / factor if assets_then else Decimal(0)
230+
now_norm = Decimal(assets_now) / factor if assets_now else Decimal(0)
231+
232+
# Totals since inception (to latest)
233+
est_total_assets_norm = agg_now.withdraws_assets_norm + now_norm
234+
total_profit_norm = est_total_assets_norm - agg_now.deposits_assets_norm
235+
236+
# Post-snapshot flows
237+
agg_then = aggs_then.get(owner, UserAgg())
238+
deposited_since_snap = agg_now.deposits_assets_norm - agg_then.deposits_assets_norm
239+
withdrawn_since_snap = agg_now.withdraws_assets_norm - agg_then.withdraws_assets_norm
240+
if deposited_since_snap < 0:
241+
deposited_since_snap = Decimal(0)
242+
if withdrawn_since_snap < 0:
243+
withdrawn_since_snap = Decimal(0)
244+
245+
# From-snapshot profit = (current_withdrawable + withdrawn_after_snap) - (snapshot_balance + deposited_after_snap)
246+
from_snap_profit = (now_norm + withdrawn_since_snap) - (
247+
then_norm + deposited_since_snap
248+
)
249+
250+
# Relative growth (ratios, not percents)
251+
snap_denom = then_norm + deposited_since_snap
252+
snap_rel_growth = (from_snap_profit / snap_denom) if snap_denom > 0 else Decimal(0)
253+
all_rel_growth = (
254+
(total_profit_norm / agg_now.deposits_assets_norm)
255+
if agg_now.deposits_assets_norm > 0
192256
else Decimal(0)
193257
)
194-
est_total_assets_norm = agg.withdraws_assets_norm + unrealized_assets_norm
195-
est_profit_norm = est_total_assets_norm - agg.deposits_assets_norm
196258

197-
all_rows.append(
259+
rows.append(
198260
{
261+
# Requested metrics only
199262
"pool": pool_name,
200263
"owner": owner,
201-
"deposited_assets": str(agg.deposits_assets_norm),
202-
"withdrawn_assets": str(agg.withdraws_assets_norm),
203-
"shares_in": str(agg.shares_in),
204-
"shares_out": str(agg.shares_out),
205-
"net_shares": str(net_shares),
206-
"est_assets_unrealized": str(unrealized_assets_norm),
207-
"est_total_assets": str(est_total_assets_norm),
208-
"est_profit": str(est_profit_norm),
209-
"est_profit_relative": str(est_profit_norm / agg.deposits_assets_norm * 100),
264+
"deposited_assets": str(agg_now.deposits_assets_norm),
265+
"withdrawn_assets": str(agg_now.withdraws_assets_norm),
266+
"snapshot_balance": str(then_norm),
267+
"current_withdrawable": str(now_norm),
268+
"total_profit": str(total_profit_norm),
269+
"from_snap_profit": str(from_snap_profit),
270+
"snap_rel_growth": str(snap_rel_growth),
271+
"all_rel_growth": str(all_rel_growth),
210272
}
211273
)
212274

213-
# Quick summary
214-
realized = sum(Decimal(r["withdrawn_assets"]) for r in all_rows if r["pool"] == pool_name)
215-
unrealized = sum(
216-
Decimal(r["est_assets_unrealized"]) for r in all_rows if r["pool"] == pool_name
217-
)
218-
deposited = sum(Decimal(r["deposited_assets"]) for r in all_rows if r["pool"] == pool_name)
219-
rel = realized + unrealized - deposited
220-
rel_pct = (rel / deposited * 100) if deposited != 0 else Decimal(0)
275+
# 6) Quick pool summary over the snapshot owner set
276+
total_then = sum(Decimal(r["snapshot_balance"]) for r in rows if r["pool"] == pool_name)
277+
total_now = sum(Decimal(r["current_withdrawable"]) for r in rows if r["pool"] == pool_name)
278+
pool_growth = total_now - total_then
279+
pool_growth_rel = (pool_growth / total_then) if total_then != 0 else Decimal(0)
221280
print(
222-
f" Summary: deposited={deposited} realized={realized} unrealized={unrealized} est_profit={rel} (relative={rel_pct:.4f}%)"
281+
f" Summary (snapshot owners): then={total_then} now={total_now} growth={pool_growth} (rel={pool_growth_rel:.6f})"
223282
)
224283

225-
# Optional: write CSV
226-
# filepath + csv
284+
# Output CSV next to this script
227285
script_dir = os.path.dirname(os.path.abspath(__file__))
228-
out_csv = script_dir + "/out_data.csv"
229-
if out_csv:
230-
import csv
231-
232-
header = (
233-
list(all_rows[0].keys())
234-
if all_rows
235-
else [
236-
"pool",
237-
"owner",
238-
"deposited_assets",
239-
"withdrawn_assets",
240-
"shares_in",
241-
"shares_out",
242-
"net_shares",
243-
"est_assets_unrealized",
244-
"est_total_assets",
245-
"est_profit",
246-
"est_profit_relative",
247-
]
248-
)
249-
with open(out_csv, "w", newline="") as f:
250-
w = csv.DictWriter(f, fieldnames=header)
251-
w.writeheader()
252-
for row in all_rows:
253-
w.writerow(row)
254-
print(f"\nWrote per-user summary to {out_csv}")
255-
else:
256-
# Print top few for a quick look
257-
print("\nSample rows:")
258-
for row in all_rows[:10]:
259-
print(row)
286+
# Match the location and style of all_users_check output
287+
out_csv = os.path.join(script_dir, "out_data.csv")
288+
header = (
289+
list(rows[0].keys())
290+
if rows
291+
else [
292+
"pool",
293+
"owner",
294+
"deposited_assets",
295+
"withdrawn_assets",
296+
"snapshot_balance",
297+
"current_withdrawable",
298+
"total_profit",
299+
"from_snap_profit",
300+
"snap_rel_growth",
301+
"all_rel_growth",
302+
]
303+
)
304+
305+
with open(out_csv, "w", newline="") as f:
306+
writer = csv.DictWriter(f, fieldnames=header)
307+
writer.writeheader()
308+
for row in rows:
309+
writer.writerow(row)
310+
print(f"\nWrote per-user summary with requested metrics to {out_csv}")
260311

261312

262313
if __name__ == "__main__":

0 commit comments

Comments
 (0)