Skip to content

Instantly share code, notes, and snippets.

@devinaconley
Created December 20, 2022 20:17
Show Gist options
  • Save devinaconley/78fcbfbe2e1f51651fc94b641c4d3983 to your computer and use it in GitHub Desktop.
Save devinaconley/78fcbfbe2e1f51651fc94b641c4d3983 to your computer and use it in GitHub Desktop.
simulate GYSR v1 token economics
"""
simulate GYSR token economics
"""
# lib
import math
import random
import itertools
import numpy as np
from scipy.integrate import odeint
from bokeh.io import show
from bokeh.layouts import column
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import HoverTool, CrosshairTool
from bokeh.palettes import Category10_10 as palette
# constants
SEED = 1111
TOTAL_SUPPLY = 100e6 # total GYSR supply
INITIAL_PRICE = 20 * 10 / TOTAL_SUPPLY # ETH / GYSR
K_P = 0.00001
K_L = 0.0001
K_N = 0.001
TIME_STEP = 60 * 60
NUM_GEYSERS = 10
TOKEN_VALUE = (-5, -1) # powers of 10 (ETH)
FUNDING_AMOUNT = (2, 5) # powers of 10 (ETH)
FUNDING_PERIOD = (365 * 24 * 60 * 60, 365 * 24 * 60 * 60) # seconds
UNSTAKE_SHARE = (-3, -1) # powers of 10
UNSTAKE_PERIOD = (60, 3600)
class Geyser(object):
def __init__(
self,
token_value: float, # eth
funding_amount: float, # tokens
funding_period: float, # seconds
unstake_share: float, # [0,1]
unstake_period: float # seconds
):
self.token_value = token_value
self.funding_amount = funding_amount
self.funding_period = funding_period
self.unstake_share = unstake_share
self.unstake_period = unstake_period
def gysr_bonus(proportion: float, gysr: float):
"""
helper function to compute bonus for a certain amount of GYSR
:param proportion
:param gysr:
:return:
"""
if gysr < 1:
return 1.0
# usage = self.total_rewards_gysr / self.total_rewards
return 1.0 + math.log10((0.01 + gysr) / (0.01 + proportion))
def deriv(y: [float], t: float, geysers: [Geyser]):
"""
derivative of geyser economics
:param y:
:param t:
:param geysers:
:return:
"""
p = y[0]
dldt = []
dndt = []
market_cap = p * TOTAL_SUPPLY
# total_locked_delta_value = 0
total_delta_value = 0
for i, g in enumerate(geysers):
l = y[i + 1]
n = y[len(geysers) + i + 1]
# delta rewards
if t < g.funding_period:
unlocked = (g.unstake_period / g.funding_period) * g.funding_amount
else:
unlocked = 0
raw_rewards = g.unstake_share * unlocked
inflated_rewards = g.unstake_share * gysr_bonus(l, n) / (
1.0 - g.unstake_share + g.unstake_share * gysr_bonus(l, n)
) * unlocked
delta_rewards = g.token_value * (inflated_rewards - raw_rewards) # eth
# geyser specific differentials
dldt_raw = K_L * (delta_rewards / n - p)
if dldt_raw > 0:
dldt_raw *= (1.0 - l)
else:
dldt_raw *= (l - 0.0)
dldt.append(dldt_raw)
dndt_raw = K_N * (delta_rewards / n - p)
if dndt_raw < 0:
dndt_raw *= (n - 1.0)
dndt.append(dndt_raw)
# remaining gysr boosted locked
# locked = (g.funding_period - t) / g.funding_period * g.funding_amount
# delta_locked = g.unstake_share * gysr_bonus(l, n) / (
# 1.0 - g.unstake_share + g.unstake_share * gysr_bonus(l, n)
# ) * locked
# total_locked_delta_value += g.token_value * delta_locked
delta_value = g.unstake_share * gysr_bonus(l, n) / (
1.0 - g.unstake_share + g.unstake_share * gysr_bonus(l, n)
) * g.funding_amount
total_delta_value += g.token_value * delta_value
dpdt = K_P * (total_delta_value - market_cap) / TOTAL_SUPPLY
return dpdt, *dldt, *dndt
def main():
# set random seed
random.seed(SEED)
# generate some random geyser
geysers = []
for i in range(NUM_GEYSERS):
pool_total = 10 ** random.uniform(*FUNDING_AMOUNT)
tkn_val = 10 ** random.uniform(*TOKEN_VALUE)
geysers.append(
Geyser(
tkn_val,
pool_total / tkn_val,
random.uniform(*FUNDING_PERIOD),
10 ** random.uniform(*UNSTAKE_SHARE),
random.uniform(*UNSTAKE_PERIOD)
)
)
# setup initial conditions
y0 = [INITIAL_PRICE]
y0.extend([0.0] * len(geysers))
y0.extend([1.0] * len(geysers))
y0 = tuple(y0)
# do ODE integration
duration = max([g.funding_period for g in geysers])
t = np.linspace(0, duration, int(duration / TIME_STEP))
res = odeint(deriv, y0, t, args=(geysers,))
print(res)
# setup plotting data
data = {'t': t, 'price': res[:, 0]}
for i, g in enumerate(geysers):
data['l_{}'.format(i)] = res[:, i + 1]
data['n_{}'.format(i)] = res[:, len(geysers) + i + 1]
src = ColumnDataSource(data=data)
# plot price
fig_price = figure(title='price', plot_height=400, plot_width=1200)
l = fig_price.line('t', 'price', source=src, color='deepskyblue', legend_label='GYSR/ETH')
fig_price.add_tools(HoverTool(renderers=[l], tooltips=[('price', '@price')], mode='vline'))
fig_price.add_tools(CrosshairTool(dimensions='height', line_alpha=0.3))
# plot geyser demand
fig_likelihood = figure(title='likelihood GYSR used', plot_height=400, plot_width=1200)
colors = itertools.cycle(palette)
for i, g in enumerate(geysers):
l = fig_likelihood.line('t', 'l_{}'.format(i), color=next(colors), source=src)
fig_likelihood.add_tools(
HoverTool(renderers=[l], tooltips=[('l_{}'.format(i), '@l_{}'.format(i))], mode='vline'))
fig_likelihood.add_tools(CrosshairTool(dimensions='height', line_alpha=0.3))
fig_amount = figure(title='expected amount GYSR used', plot_height=400, plot_width=1200)
colors = itertools.cycle(palette)
for i, g in enumerate(geysers):
l = fig_amount.line('t', 'n_{}'.format(i), color=next(colors), source=src)
fig_amount.add_tools(HoverTool(renderers=[l], tooltips=[('n_{}'.format(i), '@n_{}'.format(i))], mode='vline'))
fig_amount.add_tools(CrosshairTool(dimensions='height', line_alpha=0.3))
# display figure
show(column(fig_price, fig_likelihood, fig_amount))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment