lockdrop-simulation/lockdrop/calculations.py
Prathamesh Musale 652c69cbee Move lockdrop calculations to a module and add a experimentation notebook (#2)
- Refactor lockdrop calculations and other helper code from the simulation notebook to a python module
- Add a notebook for experimenting with lockdrop calculations
  - Add widget buttons to run calculations and export results
  - Add a script a to setup and run the notebook

Reviewed-on: #2
Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-committed-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
2025-08-13 11:56:52 +00:00

186 lines
8.7 KiB
Python

"""
Core lockdrop calculation functions.
This module contains the main mathematical functions for calculating token allocations,
penalties, bonuses, and final distributions.
"""
from decimal import Decimal, ROUND_DOWN
from .constants import (
TOKEN_PRECISION, LOCKDROP_ALLOCATION, STAR_ALLOCATION_PERCENT,
GALAXY_ALLOCATION_PERCENT,
LOCKDROP_DURATION_BLOCKS, PENALTY_RATES
)
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,
'rounding_error_per_star': rounding_error_per_star,
'rounding_error_per_galaxy': rounding_error_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