Created
December 20, 2022 20:17
-
-
Save devinaconley/78fcbfbe2e1f51651fc94b641c4d3983 to your computer and use it in GitHub Desktop.
simulate GYSR v1 token economics
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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