Refactor lockdrop calculations to a module

This commit is contained in:
Prathamesh Musale 2025-08-12 15:09:55 +05:30
parent 779b091ccd
commit 48bb2f7b83
4 changed files with 1111 additions and 2 deletions

View File

@ -424,7 +424,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"id": "26573d6b",
"metadata": {},
"outputs": [
@ -489,7 +489,7 @@
"# Load events from watcher file\n",
"import os\n",
"\n",
"watcher_events_path = os.path.join(os.getenv('GENERATED_DIR', './generated'), 'watcher-events.json')\n",
"watcher_events_path = os.path.join(os.getenv('GENERATED_DIR', 'generated'), 'watcher-events.json')\n",
"events = load_watcher_events(watcher_events_path)\n",
"lock_stats = analyze_lockdrop_events(events)\n",
"\n",

608
lockdrop-calculations.ipynb Normal file
View File

@ -0,0 +1,608 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "experimental-header",
"metadata": {},
"source": [
"# Z Token Lockdrop Distribution\n",
"\n",
"This notebook allows you to experiment with different participation distributions to see how they affect token allocations, bonus pools, and the penalty system.\n",
"\n",
"**Features:**\n",
"- Adjust star and galaxy participation numbers by lock duration\n",
"- Real-time calculation of allocations and bonus pools\n",
"- Visualization of results"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "setup-imports",
"metadata": {},
"outputs": [],
"source": [
"# Import shared calculation module\n",
"from lockdrop_calculations import (\n",
" configure_matplotlib, print_constants_summary, calculate_dynamic_allocations,\n",
" calculate_bonus_pools, calculate_final_allocations, generate_test_output,\n",
" print_analysis_tables, create_visualization, PENALTY_RATES, NUM_STARS, NUM_GALAXIES\n",
")\n",
"import json\n",
"from decimal import Decimal\n",
"import pandas as pd\n",
"import ipywidgets as widgets\n",
"from IPython.display import display, clear_output\n",
"\n",
"# Configure matplotlib\n",
"configure_matplotlib()"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "show-constants",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"================================================================================\n",
"📊 $Z LOCKDROP DISTRIBUTION - CORE CONSTANTS\n",
"================================================================================\n",
"\n",
"🔒 LOCKDROP ALLOCATION\n",
" Parameter Value\n",
"Total Supply (1 $Z per Urbit ID) 4,294,967,296\n",
" Lockdrop Allocation % 30.0%\n",
" Lockdrop Allocation ($Z) 1,288,490,188.8\n",
"\n",
"⭐ URBIT POINTS DISTRIBUTION\n",
"Point Type Count Allocation %\n",
" Galaxies 256 0.39%\n",
" Stars 65,280 99.61%\n",
" Planets 4,294,901,760 0%\n",
"\n",
"⏱️ LOCKDROP PARAMETERS\n",
" Parameter Value\n",
" Block Duration 2 seconds\n",
"Max Point Lock Duration (5 yrs) 157,788,000 seconds\n",
" Total Blocks 78,894,000\n",
" Star Allocation % 0.996093\n",
" Galaxy Allocation % 0.003906\n",
"\n",
"================================================================================\n"
]
}
],
"source": [
"# Display core constants\n",
"print_constants_summary()"
]
},
{
"cell_type": "markdown",
"id": "input-section-header",
"metadata": {},
"source": [
"## 🎛️ Experimental Parameters\n",
"\n",
"Adjust the participation numbers below to experiment with different scenarios. The default values represent a balanced distribution scenario."
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "input-parameters",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"🎯 PARTICIPATION INPUT CONTROLS\n",
"==================================================\n",
"\n",
"⭐ STAR PARTICIPATION\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "79244d07df4e4ee6923dda69c1febc2e",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(IntText(value=8000, description='1 Year:', style=DescriptionStyle(description_width='80px')), I…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🌌 GALAXY PARTICIPATION\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "4071b25411c44a67b26f5924a2507c0c",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"VBox(children=(IntText(value=40, description='1 Year:', style=DescriptionStyle(description_width='80px')), Int…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "1c785fe3828d4d5eb62a9f35755f29f8",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Button(button_style='success', description='🔄 Calculate Allocations', layout=Layout(margin='20px 0', width='20…"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🌌 CALCULATIONS\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "bc3937e163a34ff58f215f94a36aee3e",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Define reasonable default values\n",
"DEFAULT_PARTICIPANTS = {\n",
" 'stars_1_years': 8000,\n",
" 'stars_2_years': 8000,\n",
" 'stars_3_years': 8000,\n",
" 'stars_4_years': 8000,\n",
" 'stars_5_years': 8000,\n",
" 'galaxies_1_years': 40,\n",
" 'galaxies_2_years': 40,\n",
" 'galaxies_3_years': 40,\n",
" 'galaxies_4_years': 40,\n",
" 'galaxies_5_years': 40\n",
"}\n",
"\n",
"# Create input widgets\n",
"print(\"🎯 PARTICIPATION INPUT CONTROLS\")\n",
"print(\"=\"*50)\n",
"\n",
"# Star participation widgets\n",
"print(\"\\n⭐ STAR PARTICIPATION\")\n",
"stars_1_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['stars_1_years'], description='1 Year:', style={'description_width': '80px'})\n",
"stars_2_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['stars_2_years'], description='2 Years:', style={'description_width': '80px'})\n",
"stars_3_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['stars_3_years'], description='3 Years:', style={'description_width': '80px'})\n",
"stars_4_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['stars_4_years'], description='4 Years:', style={'description_width': '80px'})\n",
"stars_5_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['stars_5_years'], description='5 Years:', style={'description_width': '80px'})\n",
"\n",
"star_controls = widgets.VBox([stars_1_years, stars_2_years, stars_3_years, stars_4_years, stars_5_years])\n",
"display(star_controls)\n",
"\n",
"print(\"\\n🌌 GALAXY PARTICIPATION\")\n",
"galaxies_1_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['galaxies_1_years'], description='1 Year:', style={'description_width': '80px'})\n",
"galaxies_2_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['galaxies_2_years'], description='2 Years:', style={'description_width': '80px'})\n",
"galaxies_3_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['galaxies_3_years'], description='3 Years:', style={'description_width': '80px'})\n",
"galaxies_4_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['galaxies_4_years'], description='4 Years:', style={'description_width': '80px'})\n",
"galaxies_5_years = widgets.IntText(value=DEFAULT_PARTICIPANTS['galaxies_5_years'], description='5 Years:', style={'description_width': '80px'})\n",
"\n",
"galaxy_controls = widgets.VBox([galaxies_1_years, galaxies_2_years, galaxies_3_years, galaxies_4_years, galaxies_5_years])\n",
"display(galaxy_controls)\n",
"\n",
"# Calculate button\n",
"calculate_button = widgets.Button(description='🔄 Calculate Allocations', button_style='success', layout={'width': '200px', 'margin': '20px 0'})\n",
"display(calculate_button)\n",
"\n",
"# Output area\n",
"print(\"\\n🌌 CALCULATIONS\")\n",
"output_area = widgets.Output()\n",
"display(output_area)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "calculation-function",
"metadata": {},
"outputs": [],
"source": [
"def calculate_and_display(button=None):\n",
" \"\"\"Calculate allocations and display results based on input parameters.\"\"\"\n",
" with output_area:\n",
" clear_output(wait=True)\n",
"\n",
" # Get current values from widgets\n",
" participation_counts = {\n",
" 'stars_1_years': stars_1_years.value,\n",
" 'stars_2_years': stars_2_years.value,\n",
" 'stars_3_years': stars_3_years.value,\n",
" 'stars_4_years': stars_4_years.value,\n",
" 'stars_5_years': stars_5_years.value,\n",
" 'galaxies_1_years': galaxies_1_years.value,\n",
" 'galaxies_2_years': galaxies_2_years.value,\n",
" 'galaxies_3_years': galaxies_3_years.value,\n",
" 'galaxies_4_years': galaxies_4_years.value,\n",
" 'galaxies_5_years': galaxies_5_years.value\n",
" }\n",
"\n",
" # Validate inputs\n",
" total_stars = sum(participation_counts[key] for key in participation_counts if 'stars' in key)\n",
" total_galaxies = sum(participation_counts[key] for key in participation_counts if 'galaxies' in key)\n",
"\n",
" if total_stars > NUM_STARS:\n",
" print(f\"⚠️ ERROR: Total stars ({total_stars:,}) exceeds maximum available ({NUM_STARS:,})\")\n",
" return\n",
"\n",
" if total_galaxies > NUM_GALAXIES:\n",
" print(f\"⚠️ ERROR: Total galaxies ({total_galaxies:,}) exceeds maximum available ({NUM_GALAXIES:,})\")\n",
" return\n",
"\n",
" if total_stars == 0 and total_galaxies == 0:\n",
" print(\"⚠️ ERROR: Must have at least some participants\")\n",
" return\n",
"\n",
" try:\n",
" # Perform calculations\n",
" print(\"🔄 Calculating allocations...\\n\")\n",
"\n",
" allocation_data = calculate_dynamic_allocations(participation_counts)\n",
" bonus_data = calculate_bonus_pools(allocation_data)\n",
" final_data = calculate_final_allocations(allocation_data, bonus_data)\n",
"\n",
" # Display results\n",
" print_analysis_tables(allocation_data, bonus_data, final_data)\n",
"\n",
" # Show penalty rates for reference\n",
" print(\"\\n📋 PENALTY SYSTEM REFERENCE\")\n",
" penalty_df = pd.DataFrame({\n",
" 'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'],\n",
" 'Penalty Rate': [f\"{PENALTY_RATES[year]:.1%}\" for year in [5, 4, 3, 2, 1]],\n",
" 'Description': ['No penalty + Bonus', '20% penalty', '40% penalty', '60% penalty', '80% penalty']\n",
" })\n",
" print(penalty_df.to_string(index=False))\n",
"\n",
" # Create visualization\n",
" print(\"\\n📊 Generating visualization...\")\n",
" create_visualization(allocation_data, final_data)\n",
"\n",
" # Calculate participation insights\n",
" star_participation_rate = Decimal(total_stars) / NUM_STARS\n",
" galaxy_participation_rate = Decimal(total_galaxies) / NUM_GALAXIES\n",
"\n",
" print(\"\\n\" + \"=\"*80)\n",
" print(\"🎯 EXPERIMENT INSIGHTS\")\n",
" print(\"=\"*80)\n",
"\n",
" insights_df = pd.DataFrame({\n",
" 'Metric': [\n",
" 'Total Participants',\n",
" 'Star Participation Rate',\n",
" 'Galaxy Participation Rate',\n",
" '5-Year Star Bonus',\n",
" '5-Year Galaxy Bonus',\n",
" 'Highest Individual Allocation',\n",
" 'Lowest Individual Allocation'\n",
" ],\n",
" 'Value': [\n",
" f\"{total_stars + total_galaxies:,}\",\n",
" f\"{star_participation_rate:.1%}\",\n",
" f\"{galaxy_participation_rate:.1%}\",\n",
" f\"{float(bonus_data['bonus_per_star_5_years']):,.2f} $Z\" if participation_counts['stars_5_years'] > 0 else \"N/A (no 5Y stars)\",\n",
" f\"{float(bonus_data['bonus_per_galaxy_5_years']):,.2f} $Z\" if participation_counts['galaxies_5_years'] > 0 else \"N/A (no 5Y galaxies)\",\n",
" f\"{max(float(final_data['final_star_allocations'][5]), float(final_data['final_galaxy_allocations'][5])):,.2f} $Z\",\n",
" f\"{min(float(final_data['final_star_allocations'][1]), float(final_data['final_galaxy_allocations'][1])):,.2f} $Z\"\n",
" ]\n",
" })\n",
" print(insights_df.to_string(index=False))\n",
" print(\"\\n\" + \"=\"*80)\n",
"\n",
" except Exception as e:\n",
" print(f\"❌ Error during calculation: {str(e)}\")\n",
" import traceback\n",
" traceback.print_exc()\n",
"\n",
"# Connect button to calculation function\n",
"calculate_button.on_click(calculate_and_display)"
]
},
{
"cell_type": "markdown",
"id": "preset-scenarios-header",
"metadata": {},
"source": [
"## 🎮 Preset Scenarios\n",
"\n",
"Try these interesting scenarios by running the code below:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "preset-scenarios",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"📋 AVAILABLE PRESET SCENARIOS:\n",
"==================================================\n",
"\n",
"• balanced: Balanced distribution across all lock periods\n",
"\n",
"• five_year_focused: Most participants choose 5-year lock (maximum bonus scenario)\n",
"\n",
"• short_term_focused: Most participants choose shorter locks (high penalty scenario)\n",
"\n",
"• polarized: Split between 1-year and 5-year locks (maximum bonus per 5Y participant)\n",
"\n",
"• low_participation: Low overall participation scenario\n",
"\n",
"🎯 To load a scenario, call: load_scenario('scenario_name')\n",
"\n",
"Example: load_scenario('five_year_focused')\n"
]
}
],
"source": [
"# Define preset scenarios\n",
"SCENARIOS = {\n",
" 'balanced': {\n",
" 'description': 'Balanced distribution across all lock periods',\n",
" 'params': {\n",
" 'stars_1_years': 8000, 'stars_2_years': 8000, 'stars_3_years': 8000, 'stars_4_years': 8000, 'stars_5_years': 8000,\n",
" 'galaxies_1_years': 40, 'galaxies_2_years': 40, 'galaxies_3_years': 40, 'galaxies_4_years': 40, 'galaxies_5_years': 40\n",
" }\n",
" },\n",
" 'five_year_focused': {\n",
" 'description': 'Most participants choose 5-year lock (maximum bonus scenario)',\n",
" 'params': {\n",
" 'stars_1_years': 2000, 'stars_2_years': 2000, 'stars_3_years': 3000, 'stars_4_years': 5000, 'stars_5_years': 28000,\n",
" 'galaxies_1_years': 10, 'galaxies_2_years': 10, 'galaxies_3_years': 20, 'galaxies_4_years': 30, 'galaxies_5_years': 130\n",
" }\n",
" },\n",
" 'short_term_focused': {\n",
" 'description': 'Most participants choose shorter locks (high penalty scenario)',\n",
" 'params': {\n",
" 'stars_1_years': 20000, 'stars_2_years': 15000, 'stars_3_years': 4000, 'stars_4_years': 800, 'stars_5_years': 200,\n",
" 'galaxies_1_years': 100, 'galaxies_2_years': 80, 'galaxies_3_years': 15, 'galaxies_4_years': 3, 'galaxies_5_years': 2\n",
" }\n",
" },\n",
" 'polarized': {\n",
" 'description': 'Split between 1-year and 5-year locks (maximum bonus per 5Y participant)',\n",
" 'params': {\n",
" 'stars_1_years': 25000, 'stars_2_years': 0, 'stars_3_years': 0, 'stars_4_years': 0, 'stars_5_years': 15000,\n",
" 'galaxies_1_years': 150, 'galaxies_2_years': 0, 'galaxies_3_years': 0, 'galaxies_4_years': 0, 'galaxies_5_years': 50\n",
" }\n",
" },\n",
" 'low_participation': {\n",
" 'description': 'Low overall participation scenario',\n",
" 'params': {\n",
" 'stars_1_years': 1000, 'stars_2_years': 800, 'stars_3_years': 600, 'stars_4_years': 400, 'stars_5_years': 200,\n",
" 'galaxies_1_years': 8, 'galaxies_2_years': 6, 'galaxies_3_years': 4, 'galaxies_4_years': 2, 'galaxies_5_years': 5\n",
" }\n",
" }\n",
"}\n",
"\n",
"def load_scenario(scenario_name):\n",
" \"\"\"Load a preset scenario into the input widgets.\"\"\"\n",
" if scenario_name not in SCENARIOS:\n",
" print(f\"❌ Unknown scenario: {scenario_name}\")\n",
" return\n",
"\n",
" scenario = SCENARIOS[scenario_name]\n",
" params = scenario['params']\n",
"\n",
" # Update widget values\n",
" stars_1_years.value = params['stars_1_years']\n",
" stars_2_years.value = params['stars_2_years']\n",
" stars_3_years.value = params['stars_3_years']\n",
" stars_4_years.value = params['stars_4_years']\n",
" stars_5_years.value = params['stars_5_years']\n",
" galaxies_1_years.value = params['galaxies_1_years']\n",
" galaxies_2_years.value = params['galaxies_2_years']\n",
" galaxies_3_years.value = params['galaxies_3_years']\n",
" galaxies_4_years.value = params['galaxies_4_years']\n",
" galaxies_5_years.value = params['galaxies_5_years']\n",
"\n",
" print(f\"✅ Loaded scenario: {scenario_name}\")\n",
" print(f\"📝 Description: {scenario['description']}\")\n",
" print(\"\\n🔄 Click 'Calculate Allocations' button above to see results!\")\n",
"\n",
"# Display available scenarios\n",
"print(\"📋 AVAILABLE PRESET SCENARIOS:\")\n",
"print(\"=\"*50)\n",
"for name, scenario in SCENARIOS.items():\n",
" print(f\"\\n• {name}: {scenario['description']}\")\n",
"\n",
"print(\"\\n🎯 To load a scenario, call: load_scenario('scenario_name')\")\n",
"print(\"\\nExample: load_scenario('five_year_focused')\")"
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "load-scenario-example",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"✅ Loaded scenario: five_year_focused\n",
"📝 Description: Most participants choose 5-year lock (maximum bonus scenario)\n",
"\n",
"🔄 Click 'Calculate Allocations' button above to see results!\n"
]
}
],
"source": [
"# Load the balanced scenario as default\n",
"load_scenario('five_year_focused')"
]
},
{
"cell_type": "markdown",
"id": "export-section",
"metadata": {},
"source": [
"## 💾 Export Results\n",
"\n",
"Export your experimental results for use with the test suite:"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "export-results",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"💾 Export your current scenario results:\n",
" export_current_scenario('my_experiment.json')\n",
"\n",
"📋 Or use default filename:\n",
" export_current_scenario()\n"
]
}
],
"source": [
"def export_current_scenario(filename='lockdrop_allocations_experiment.json'):\n",
" \"\"\"Export current scenario results to JSON file.\"\"\"\n",
" # Get current values from widgets\n",
" participation_counts = {\n",
" 'stars_1_years': stars_1_years.value,\n",
" 'stars_2_years': stars_2_years.value,\n",
" 'stars_3_years': stars_3_years.value,\n",
" 'stars_4_years': stars_4_years.value,\n",
" 'stars_5_years': stars_5_years.value,\n",
" 'galaxies_1_years': galaxies_1_years.value,\n",
" 'galaxies_2_years': galaxies_2_years.value,\n",
" 'galaxies_3_years': galaxies_3_years.value,\n",
" 'galaxies_4_years': galaxies_4_years.value,\n",
" 'galaxies_5_years': galaxies_5_years.value\n",
" }\n",
"\n",
" try:\n",
" # Calculate allocations\n",
" allocation_data = calculate_dynamic_allocations(participation_counts)\n",
" bonus_data = calculate_bonus_pools(allocation_data)\n",
" final_data = calculate_final_allocations(allocation_data, bonus_data)\n",
"\n",
" # Generate test output\n",
" test_output = generate_test_output(final_data)\n",
"\n",
" # Add metadata\n",
" export_data = {\n",
" 'metadata': {\n",
" 'source': 'lockdrop-experiment.ipynb',\n",
" 'scenario': 'custom',\n",
" 'participation_counts': participation_counts\n",
" },\n",
" 'allocations': test_output\n",
" }\n",
"\n",
" # Save to file\n",
" with open(filename, 'w') as f:\n",
" json.dump(export_data, f, indent=2)\n",
"\n",
" print(f\"✅ Results exported to: {filename}\")\n",
" print(f\"📊 Total participants: {sum(participation_counts.values()):,}\")\n",
"\n",
" except Exception as e:\n",
" print(f\"❌ Export failed: {str(e)}\")\n",
"\n",
"# Usage example\n",
"print(\"💾 Export your current scenario results:\")\n",
"print(\" export_current_scenario('my_experiment.json')\")\n",
"print(\"\\n📋 Or use default filename:\")\n",
"print(\" export_current_scenario()\")"
]
},
{
"cell_type": "markdown",
"id": "tips-section",
"metadata": {},
"source": [
"## 💡 Experimentation Tips\n",
"\n",
"### Interesting Things to Try:\n",
"\n",
"1. **Bonus Pool Impact**: Set most participants to 1-2 years and a few to 5 years to see massive bonuses\n",
"2. **Participation Rate Effects**: Try very low participation vs. high participation scenarios\n",
"3. **Star vs Galaxy Balance**: Experiment with different star/galaxy ratios\n",
"4. **Edge Cases**: What happens with only 5-year participants? Only 1-year?\n",
"5. **Realistic Scenarios**: Model expected real-world behavior patterns\n",
"\n",
"### Key Metrics to Watch:\n",
"\n",
"- **5-Year Bonus Multiplier**: How much extra do 5-year participants get?\n",
"- **Participation Rates**: What percentage of each point type participates?\n",
"- **Penalty Pool Size**: How much bonus is generated from penalties?\n",
"- **Allocation Spread**: Difference between highest and lowest allocations\n",
"\n",
"### Understanding the Math:\n",
"\n",
"- Penalties from shorter lock periods create bonus pools\n",
"- All bonus pools go to 5-year participants\n",
"- More penalty = more bonus for 5-year participants\n",
"- Lower participation = higher individual allocations"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

500
lockdrop_calculations.py Normal file
View File

@ -0,0 +1,500 @@
"""
Shared lockdrop calculation functions and utilities.
This module contains the reference calculation logic for lockdrop token distribution,
penalty systems, bonus pools, and final allocations. It can be used by both
the simulation notebook and the experimental notebook.
"""
from decimal import Decimal, ROUND_DOWN, getcontext
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import json
import os
from collections import defaultdict
# Configure decimal precision
getcontext().prec = 28
getcontext().rounding = ROUND_DOWN
# Constants
TOKEN_PRECISION = Decimal('0.00000001') # 8 decimal precision
TOTAL_SUPPLY = 4294967296 # 1 $Z per Urbit ID
LOCKDROP_ALLOCATION_PERCENT = Decimal('0.3')
LOCKDROP_ALLOCATION = TOTAL_SUPPLY * LOCKDROP_ALLOCATION_PERCENT
# Points
NUM_GALAXIES = pow(2, 8)
NUM_STARS = pow(2, 16) - pow(2, 8)
NUM_PLANETS = pow(2, 32) - pow(2, 16)
# Allocation distribution
STAR_ALLOCATION_PERCENT = Decimal(NUM_STARS / pow(2, 16))
GALAXY_ALLOCATION_PERCENT = 1 - STAR_ALLOCATION_PERCENT
# Lockdrop duration (5 years including 1 leap year)
LOCKDROP_DURATION_SECONDS = 5 * 365.25 * 24 * 60 * 60
BLOCK_DURATION_SECONDS = 2
LOCKDROP_DURATION_BLOCKS = int(LOCKDROP_DURATION_SECONDS / BLOCK_DURATION_SECONDS)
# Penalty rates
PENALTY_RATES = {
5: Decimal('0'),
4: Decimal('0.2'),
3: Decimal('0.4'),
2: Decimal('0.6'),
1: Decimal('0.8')
}
def configure_matplotlib():
"""Configure matplotlib settings for consistent plots."""
plt.style.use('seaborn-v0_8')
sns.set_palette("husl")
plt.rcParams['figure.figsize'] = (12, 8)
plt.rcParams['font.size'] = 11
plt.rcParams['axes.titlesize'] = 14
plt.rcParams['axes.labelsize'] = 12
plt.rcParams['xtick.labelsize'] = 10
plt.rcParams['ytick.labelsize'] = 10
plt.rcParams['legend.fontsize'] = 10
plt.rcParams['axes.unicode_minus'] = False
try:
plt.rcParams['font.family'] = 'DejaVu Sans'
except:
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Liberation Sans', 'Arial', 'Helvetica']
def print_constants_summary():
"""Display core constants as formatted tables."""
print("=" * 80)
print("📊 $Z LOCKDROP DISTRIBUTION - CORE CONSTANTS")
print("=" * 80)
# Lockdrop Allocation Table
lockdrop_df = pd.DataFrame({
'Parameter': ['Total Supply (1 $Z per Urbit ID)', 'Lockdrop Allocation %', 'Lockdrop Allocation ($Z)'],
'Value': [f"{TOTAL_SUPPLY:,}", f"{LOCKDROP_ALLOCATION_PERCENT:.1%}", f"{LOCKDROP_ALLOCATION:,.1f}"]
})
print("\n🔒 LOCKDROP ALLOCATION")
print(lockdrop_df.to_string(index=False))
# Points Distribution Table
points_df = pd.DataFrame({
'Point Type': ['Galaxies', 'Stars', 'Planets'],
'Count': [f"{NUM_GALAXIES:,}", f"{NUM_STARS:,}", f"{NUM_PLANETS:,}"],
'Allocation %': ['0.39%', '99.61%', '0%']
})
print("\n⭐ URBIT POINTS DISTRIBUTION")
print(points_df.to_string(index=False))
# Lockdrop Parameters Table
params_df = pd.DataFrame({
'Parameter': ['Block Duration', 'Max Point Lock Duration (5 yrs)', 'Total Blocks', 'Star Allocation %', 'Galaxy Allocation %'],
'Value': [f"{BLOCK_DURATION_SECONDS} seconds", f"{LOCKDROP_DURATION_SECONDS:,.0f} seconds",
f"{LOCKDROP_DURATION_BLOCKS:,}", f"{STAR_ALLOCATION_PERCENT:.6f}", f"{GALAXY_ALLOCATION_PERCENT:.6f}"]
})
print("\n⏱️ LOCKDROP PARAMETERS")
print(params_df.to_string(index=False))
print("\n" + "="*80)
def calculate_base_allocations():
"""Calculate base allocation amounts and quanta."""
lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT
lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT
assert (lockdrop_allocation_stars + lockdrop_allocation_galaxies) == LOCKDROP_ALLOCATION, "point allocation doesn't add up"
# Max allocation per point (theoretical)
max_allocation_per_star = lockdrop_allocation_stars / NUM_STARS
max_allocation_per_galaxy = lockdrop_allocation_galaxies / NUM_GALAXIES
# Quanta calculation
z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS
z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS
# Round down to 6 decimals
adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
# Adjusted max allocation
adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS
adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS
return {
'lockdrop_allocation_stars': lockdrop_allocation_stars,
'lockdrop_allocation_galaxies': lockdrop_allocation_galaxies,
'max_allocation_per_star': max_allocation_per_star,
'max_allocation_per_galaxy': max_allocation_per_galaxy,
'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star,
'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy,
'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block,
'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block
}
def calculate_dynamic_allocations(participation_counts):
"""
Calculate allocations based on actual participation counts.
Args:
participation_counts: Dict with keys like 'stars_1_years', 'stars_2_years', etc.
"""
# Extract participation data
stars_counts = {year: Decimal(participation_counts[f'stars_{year}_years']) for year in range(1, 6)}
galaxies_counts = {year: Decimal(participation_counts[f'galaxies_{year}_years']) for year in range(1, 6)}
total_stars_locked = sum(stars_counts.values())
total_galaxies_locked = sum(galaxies_counts.values())
lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT
lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT
# Dynamic allocations based on actual participation
max_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0')
max_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0')
# Quanta calculation
z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS
z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS
# Round down to 6 decimals
adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
# Adjusted max allocation
adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS
adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS
# Rounding errors
rounding_error_per_star = max_allocation_per_star - adjusted_max_allocation_per_star
rounding_error_per_galaxy = max_allocation_per_galaxy - adjusted_max_allocation_per_galaxy
total_rounding_error_stars = lockdrop_allocation_stars - (adjusted_max_allocation_per_star * total_stars_locked)
total_rounding_error_galaxies = lockdrop_allocation_galaxies - (adjusted_max_allocation_per_galaxy * total_galaxies_locked)
return {
'stars_counts': stars_counts,
'galaxies_counts': galaxies_counts,
'total_stars_locked': total_stars_locked,
'total_galaxies_locked': total_galaxies_locked,
'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star,
'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy,
'total_rounding_error_stars': total_rounding_error_stars,
'total_rounding_error_galaxies': total_rounding_error_galaxies,
'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block,
'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block
}
def calculate_bonus_pools(allocation_data):
"""Calculate bonus pools from penalties and rounding errors."""
stars_counts = allocation_data['stars_counts']
galaxies_counts = allocation_data['galaxies_counts']
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
total_rounding_error_stars = allocation_data['total_rounding_error_stars']
total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies']
# Calculate penalty pools
star_penalty_pool = Decimal('0')
for years in [1, 2, 3, 4]:
penalty = PENALTY_RATES[years]
star_penalty_pool += adjusted_max_allocation_per_star * stars_counts[years] * penalty
galaxy_penalty_pool = Decimal('0')
for years in [1, 2, 3, 4]:
penalty = PENALTY_RATES[years]
galaxy_penalty_pool += adjusted_max_allocation_per_galaxy * galaxies_counts[years] * penalty
star_penalty_pool = star_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
galaxy_penalty_pool = galaxy_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
# Add rounding errors to bonus pools
star_bonus_pool_total = star_penalty_pool + total_rounding_error_stars
galaxy_bonus_pool_total = galaxy_penalty_pool + total_rounding_error_galaxies
# Calculate bonus per 5-year participant
stars_5_years = stars_counts[5]
galaxies_5_years = galaxies_counts[5]
bonus_per_star_5_years = star_bonus_pool_total / stars_5_years if stars_5_years > 0 else Decimal('0')
bonus_per_galaxy_5_years = galaxy_bonus_pool_total / galaxies_5_years if galaxies_5_years > 0 else Decimal('0')
return {
'star_penalty_pool': star_penalty_pool,
'galaxy_penalty_pool': galaxy_penalty_pool,
'star_bonus_pool_total': star_bonus_pool_total,
'galaxy_bonus_pool_total': galaxy_bonus_pool_total,
'bonus_per_star_5_years': bonus_per_star_5_years,
'bonus_per_galaxy_5_years': bonus_per_galaxy_5_years
}
def calculate_final_allocations(allocation_data, bonus_data):
"""Calculate final allocations for all lock periods."""
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
bonus_per_star_5_years = bonus_data['bonus_per_star_5_years']
bonus_per_galaxy_5_years = bonus_data['bonus_per_galaxy_5_years']
stars_counts = allocation_data['stars_counts']
galaxies_counts = allocation_data['galaxies_counts']
# Calculate final allocations
final_star_allocations = {}
final_galaxy_allocations = {}
for years in range(1, 6):
penalty = PENALTY_RATES[years]
if years == 5:
# 5-year participants get base + bonus
final_star_allocations[years] = (adjusted_max_allocation_per_star + bonus_per_star_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy + bonus_per_galaxy_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
else:
# Other years get penalized amounts
final_star_allocations[years] = (adjusted_max_allocation_per_star * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
# Calculate Z per block for each lock period
star_z_per_block = {}
galaxy_z_per_block = {}
for years in range(1, 6):
lock_duration_fraction = Decimal(years) / Decimal('5') # Fraction of full 5-year period
effective_blocks = LOCKDROP_DURATION_BLOCKS * lock_duration_fraction
star_z_per_block[years] = (final_star_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
galaxy_z_per_block[years] = (final_galaxy_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
# Calculate total allocations for verification
total_stars_allocation = sum(final_star_allocations[year] * stars_counts[year] for year in range(1, 6))
total_galaxies_allocation = sum(final_galaxy_allocations[year] * galaxies_counts[year] for year in range(1, 6))
return {
'final_star_allocations': final_star_allocations,
'final_galaxy_allocations': final_galaxy_allocations,
'star_z_per_block': star_z_per_block,
'galaxy_z_per_block': galaxy_z_per_block,
'total_stars_allocation': total_stars_allocation,
'total_galaxies_allocation': total_galaxies_allocation
}
def generate_test_output(final_data):
"""Generate JSON output for test validation."""
final_star_allocations = final_data['final_star_allocations']
final_galaxy_allocations = final_data['final_galaxy_allocations']
total_allocation = final_data['total_stars_allocation'] + final_data['total_galaxies_allocation']
# Convert to $sZ units (multiply by 10^8)
output = {
"stars": {
f"{year}_years": int(final_star_allocations[year] * Decimal('1e8'))
for year in range(1, 6)
},
"galaxies": {
f"{year}_years": int(final_galaxy_allocations[year] * Decimal('1e8'))
for year in range(1, 6)
},
"total": int(total_allocation * Decimal('1e8'))
}
return output
def print_analysis_tables(allocation_data, bonus_data, final_data):
"""Print comprehensive analysis tables."""
stars_counts = allocation_data['stars_counts']
galaxies_counts = allocation_data['galaxies_counts']
total_stars_locked = allocation_data['total_stars_locked']
total_galaxies_locked = allocation_data['total_galaxies_locked']
final_star_allocations = final_data['final_star_allocations']
final_galaxy_allocations = final_data['final_galaxy_allocations']
star_z_per_block = final_data['star_z_per_block']
galaxy_z_per_block = final_data['galaxy_z_per_block']
print("=" * 80)
print("📈 DYNAMIC LOCKDROP ANALYSIS")
print("=" * 80)
# Participation rates
star_participation_rate = total_stars_locked / NUM_STARS
galaxy_participation_rate = total_galaxies_locked / NUM_GALAXIES
print(f"\n📊 PARTICIPATION RATES")
print(f" Stars: {star_participation_rate:.1%} ({total_stars_locked:,}/{NUM_STARS:,})")
print(f" Galaxies: {galaxy_participation_rate:.1%} ({total_galaxies_locked:,}/{NUM_GALAXIES:,})")
# Final allocations table
print("\n⭐ FINAL STAR ALLOCATIONS")
star_final_df = pd.DataFrame({
'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'],
'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]],
'Final Allocation ($Z)': [f"{final_star_allocations[year]:.12f}" for year in [5, 4, 3, 2, 1]],
'Z per Block': [f"{star_z_per_block[year]:.12f}" for year in [5, 4, 3, 2, 1]],
'Participants': [f"{stars_counts[year]:,}" for year in [5, 4, 3, 2, 1]]
})
print(star_final_df.to_string(index=False))
print("\n🌌 FINAL GALAXY ALLOCATIONS")
galaxy_final_df = pd.DataFrame({
'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'],
'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]],
'Final Allocation ($Z)': [f"{final_galaxy_allocations[year]:.12f}" for year in [5, 4, 3, 2, 1]],
'Z per Block': [f"{galaxy_z_per_block[year]:.12f}" for year in [5, 4, 3, 2, 1]],
'Participants': [f"{galaxies_counts[year]:,}" for year in [5, 4, 3, 2, 1]]
})
print(galaxy_final_df.to_string(index=False))
# Bonus pool summary
print("\n🎁 BONUS POOL SUMMARY")
bonus_summary_df = pd.DataFrame({
'Component': ['Star Bonus Pool', 'Galaxy Bonus Pool', 'Total Bonus Distributed'],
'Value': [
f"{bonus_data['star_bonus_pool_total']:,.6f} $Z",
f"{bonus_data['galaxy_bonus_pool_total']:,.6f} $Z",
f"{bonus_data['star_bonus_pool_total'] + bonus_data['galaxy_bonus_pool_total']:,.6f} $Z"
]
})
print(bonus_summary_df.to_string(index=False))
print("\n" + "="*80)
def create_visualization(allocation_data, final_data):
"""Create comprehensive visualization plots."""
stars_counts = allocation_data['stars_counts']
galaxies_counts = allocation_data['galaxies_counts']
final_star_allocations = final_data['final_star_allocations']
final_galaxy_allocations = final_data['final_galaxy_allocations']
fig = plt.figure(figsize=(20, 10))
gs = fig.add_gridspec(2, 3, hspace=0.35, wspace=0.35)
fig.suptitle('Lockdrop Analysis', fontsize=18, fontweight='bold', y=0.98)
# 1. Star Participation Distribution
ax1 = fig.add_subplot(gs[0, 0])
star_years = [1, 2, 3, 4, 5]
star_counts = [stars_counts[year] for year in star_years]
bars1 = ax1.bar(range(len(star_years)), star_counts,
color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8)
ax1.set_xlabel('Lock Period (Years)')
ax1.set_ylabel('Number of Stars')
ax1.set_title('Star Participation by Lock Period', fontweight='bold')
ax1.set_xticks(range(len(star_years)))
ax1.set_xticklabels(star_years)
for i, count in enumerate(star_counts):
height = bars1[i].get_height()
ax1.text(bars1[i].get_x() + bars1[i].get_width()/2., height + height*0.01,
f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9)
# 2. Galaxy Participation Distribution
ax2 = fig.add_subplot(gs[0, 1])
galaxy_counts = [galaxies_counts[year] for year in star_years]
bars2 = ax2.bar(range(len(star_years)), galaxy_counts,
color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8)
ax2.set_xlabel('Lock Period (Years)')
ax2.set_ylabel('Number of Galaxies')
ax2.set_title('Galaxy Participation by Lock Period', fontweight='bold')
ax2.set_xticks(range(len(star_years)))
ax2.set_xticklabels(star_years)
for i, count in enumerate(galaxy_counts):
height = bars2[i].get_height()
ax2.text(bars2[i].get_x() + bars2[i].get_width()/2., height + height*0.01,
f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9)
# 3. Star Allocations
ax3 = fig.add_subplot(gs[0, 2])
star_alloc_values = [float(final_star_allocations[year]) for year in star_years]
bars3 = ax3.bar(range(len(star_years)), star_alloc_values,
color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8)
ax3.set_xlabel('Lock Period (Years)')
ax3.set_ylabel('Allocation per Star ($Z)')
ax3.set_title('Star Allocations by Lock Period', fontweight='bold')
ax3.set_xticks(range(len(star_years)))
ax3.set_xticklabels(star_years)
for i, allocation in enumerate(star_alloc_values):
height = bars3[i].get_height()
ax3.text(bars3[i].get_x() + bars3[i].get_width()/2., height + height*0.01,
f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9)
# 4. Galaxy Allocations
ax4 = fig.add_subplot(gs[1, 0])
galaxy_alloc_values = [float(final_galaxy_allocations[year]) for year in star_years]
bars4 = ax4.bar(range(len(star_years)), galaxy_alloc_values,
color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8)
ax4.set_xlabel('Lock Period (Years)')
ax4.set_ylabel('Allocation per Galaxy ($Z)')
ax4.set_title('Galaxy Allocations by Lock Period', fontweight='bold')
ax4.set_xticks(range(len(star_years)))
ax4.set_xticklabels(star_years)
for i, allocation in enumerate(galaxy_alloc_values):
height = bars4[i].get_height()
ax4.text(bars4[i].get_x() + bars4[i].get_width()/2., height + height*0.01,
f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9)
# Hide remaining subplots
ax5 = fig.add_subplot(gs[1, 1])
ax5.axis('off')
ax6 = fig.add_subplot(gs[1, 2])
ax6.axis('off')
plt.subplots_adjust(bottom=0.08, top=0.88, left=0.05, right=0.98)
plt.show()
def load_watcher_events_data(generated_dir='generated'):
"""Load participation data from watcher events file."""
import urbitob
watcher_events_path = os.path.join(generated_dir, 'watcher-events.json')
try:
with open(watcher_events_path, 'r') as f:
data = json.load(f)
events = data['data']['eventsInRange']
# Analyze events
lock_duration_counts = {
'star': defaultdict(int),
'galaxy': defaultdict(int)
}
for event_data in events:
if event_data['event']['__typename'] == 'PointLockedEvent':
point = event_data['event']['point']
lock_period = event_data['event']['lock_period']
point_num = urbitob.patp_to_num(point)
point_type = "galaxy" if point_num < NUM_GALAXIES else "star"
lock_duration_counts[point_type][lock_period] += 1
# Extract counts
result = {}
for years in [1, 2, 3, 4, 5]:
result[f'stars_{years}_years'] = lock_duration_counts['star'][years]
result[f'galaxies_{years}_years'] = lock_duration_counts['galaxy'][years]
return result, True
except FileNotFoundError:
print(f"⚠️ Watcher events file not found at {watcher_events_path}")
return None, False
except Exception as e:
print(f"⚠️ Error loading watcher events: {e}")
return None, False

View File

@ -6,3 +6,4 @@ numpy
urbitob
tabulate
requests
ipywidgets