import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
%matplotlib inline
For a detailed Treatment of properties of Bonding Curves see: From Curved Bonding to Configuration Spaces and subsequent Publications on the subject appearing in the Proceedings of IEEE ICBC 2020 and MARBLE 2020.
Broad Mapping of the Grassroots Economics CIC Ecosystem including the CIC Users, the operators of the CIC Program and the underlying smart contracts:
from IPython.display import display, Image
display(Image(filename='CICecosystem.jpeg'))
Zooming in on the Bonding Curve Smart Contract Subsystem:
display(Image(filename='CICecosubsystem.jpeg'))
Explicit mapping of Conservation Equations which will regulate the supply of CIC tokens relative to xDAI reserves.
display(Image(filename='GrassrootsEconomicsCICcontractconservation.jpeg'))
∗ The spot price is the limiting price for both the Bond-to-Mint and the Burn-to-Withdraw Mechanisms in the case with no fees. Realized prices account for slippage and fees, see references.
† Bonding Curves such as the one being employed by the CIC systems are tools which enforce the Reserve Ratio to be a constant ρ∈(0,1), also called the connector weight. In this work we work with the "curvature" κ=1ρ=P⋅SR in order to align notation with associated academic work.
Consider the Conservation Function:
In the absense of Fees:
display(Image(filename='CICinvariant.jpeg'))
In the presence of Fees:
Economic Systems](https://arxiv.org/pdf/1807.00955.pdf) which was presented at the International Conference on Complex Systems, May 2018.
#value function for a given state (R,S)
def invariant(R,S,kappa):
return (S**kappa)/R
#given a value function (parameterized by kappa)
#and an invariant coeficient V
#return Supply S as a function of reserve R
def supply(R, kappa, V):
return (V*R)**(1/kappa)
#given a value function (parameterized by kappa)
#and an invariant coeficient V
#return a spot price P as a function of reserve R
def spot_price(R, kappa, V):
return kappa*R**((kappa-1)/kappa)/V**(1/kappa)
#for a given state (R,S)
#given a value function (parameterized by kappa and phi)
#and an invariant coeficient V
#deposit deltaR to Mint deltaS
#with realized price deltaR/deltaS
def mint(deltaR, R,S, kappa, V, phi):
deltaS = (V*(R+(1-phi)*deltaR))**(1/kappa)-S
realized_price = deltaR/deltaS
return deltaS, realized_price
#for a given state (R,S)
#given a value function (parameterized by kappa and phi)
#and an invariant coeficient V
#burn deltaS to Withdraw deltaR
#with realized price deltaR/deltaS
def withdraw(deltaS, R,S, kappa, V0, phi):
deltaR = (R-((S-deltaS)**kappa)/(V0))*(1-phi)
realized_price = deltaR/deltaS
return deltaR, realized_price
#for a given state (R,S)
#given a value function (parameterized by kappa and phi)
#and an invariant coeficient V
#computed based on desired withdraw deltaR
#with realized price deltaR/deltaS
def withdrawR(deltaR, R,S, kappa, V, phi):
deltaS = S-(V*(R-(1-phi)*deltaR))**(1/kappa)
realized_price = deltaR/deltaS
return deltaS, realized_price
R0 = 40 #thousand xDAI
kappa = 4 #leverage
P0 = 1/100 #initial price
S0 = kappa*R0/P0
p0 = R0/S0
phi = .01
#initial value of conservation function
V0 = invariant(R0,S0,kappa)
reserve = np.arange(0,500,.05)
supp = np.array([supply(r,kappa, V0) for r in reserve])
price = np.array([spot_price(r,kappa, V0) for r in reserve])
fig, ax1 = plt.subplots(figsize=(10,5))
color = 'tab:red'
ax1.set_xlabel('Reserve (Thousands)')
ax1.set_ylabel('Supply (Thousands)', color=color)
ax1.plot(reserve, supp,'-', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color = 'tab:blue'
ax2.set_ylabel('Price in xDAI per CIC token', color=color) # we already handled the x-label with ax1
ax2.plot(reserve, price,'-.', color=color)
ax2.tick_params(axis='y', labelcolor=color)
ax1.vlines(R0,0,S0, alpha=.5)
ax1.scatter(R0,S0, color='red', marker='D')
# ax1.text(R0+.02*reserve[-1], .9*supp[-1], "Initial Value R0="+str(int(100*R0)/100)+" million Reserve Units")
# ax1.text(R0+.02*reserve[-1], .70*supp[-1], "Initial Value S0="+str(S0)+" million Tokens")
ax1.text(-.4, .9*supp[-1], "R0="+str(int(100*R0)/100)+" thousand xDAI")
ax1.text(-.4, .80*supp[-1], "S0="+str(S0)+" thousand CIC Tokens")
#ax1.hlines(S0,0,R0)
# ax2.text(R0+.02*reserve[-1], price[25], "Initial Value P0="+str(spot_price(R0,kappa,V0)))
# ax2.text(R0+.02*reserve[-1], price[25]/10, "where P_hatch="+str(p0))
ax2.text(R0+.04*reserve[-1], price[25], "P0="+str(spot_price(R0,kappa,V0))+" where P_hatch="+str(p0))
ax2.scatter(R0,spot_price(R0,kappa,V0), color=color)
plt.title('Bonding Curve with Conservation Function V = S^'+str(kappa)+'/R')
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()
The figure above represents the base case as determined by the originally suggested values by the stakeholder.
fig, ax1 = plt.subplots(figsize=(10,5))
cp = 100
color = 'tab:red'
ax1.set_xlabel('Supply (Thousands)')
ax1.set_ylabel('Reserve (Thousands)', color=color)
ax1.plot(supp[cp:], reserve[cp:],'--', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color = 'tab:blue'
ax2.set_ylabel('Price in xDAI per CIC Token', color=color) # we already handled the x-label with ax1
ax2.plot(supp[cp:], price[cp:],'-.', color=color)
ax2.tick_params(axis='y', labelcolor=color)
ax1.vlines(S0,0,reserve[-1], alpha=.5)
ax1.text(S0*1.02, reserve[-1], "S0="+str(int(100*S0)/100)+" Thousands of CIC tokens")
ax1.text(S0*1.02, .95*reserve[-1], "R0="+str(R0)+" Thousands of xDAI")
#ax1.hlines(S0,0,R0)
ax2.text(S0*1.02, price[3], "P0="+str(spot_price(R0,kappa,V0))+" where P_hatch="+str(p0))
plt.title('Bonding Curve with Conservation Function V= S^'+str(kappa)+'/R')
fig.tight_layout() # otherwise the right y-label is slightly clipped
plt.show()
#given V0 and kappa
#sweep the reserve
reserve = None
reserve = np.arange(.01,100,.01)
price = np.array([spot_price(r,kappa, V0) for r in reserve])
#realized price for withdrawing burning .1% of tokens (without fee)
burn_price=[withdraw(supply(r,kappa,V0)/1000, r,supply(r,kappa,V0), kappa, V0, 0)[1] for r in reserve]
#realized price for depositing .1% more Xdai into the reserve (without fee)
mint_price=[mint(r/1000, r, supply(r,kappa,V0), kappa, V0, 0)[1] for r in reserve]
#realized price for withdrawing .1% of the Xdai from the reserve (without fee)
withdraw_price=[withdrawR(r/1000, r, supply(r,kappa,V0), kappa, V0, 0)[1] for r in reserve]
#realized price for depositing .1% more Xdai into the reserve (with fee)
mint_price_fee=[mint(r/1000, r, supply(r,kappa,V0), kappa, V0, phi)[1] for r in reserve]
#realized price for withdrawing .1% of the Xdai from the reserve (with fee)
withdraw_price_fee=[withdrawR(r/1000, r, supply(r,kappa,V0), kappa, V0, phi)[1] for r in reserve]
#from IPython.display import Image
#Image(filename='slippage.jpeg')
pdf = pd.DataFrame({'reserve':reserve, 'spot_price':price, '.1% mint_price':mint_price,'.1% withdraw_price':withdraw_price,'.1% mint_price w/fee':mint_price_fee,'.1% withdraw_price w/fee':withdraw_price_fee })
pdf.head()
reserve | spot_price | .1% mint_price | .1% withdraw_price | .1% mint_price w/fee | .1% withdraw_price w/fee | |
---|---|---|---|---|---|---|
0 | 0.01 | 0.000020 | 0.000020 | 0.000020 | 0.000020 | 0.000020 |
1 | 0.02 | 0.000033 | 0.000033 | 0.000033 | 0.000034 | 0.000034 |
2 | 0.03 | 0.000045 | 0.000045 | 0.000045 | 0.000046 | 0.000046 |
3 | 0.04 | 0.000056 | 0.000056 | 0.000056 | 0.000057 | 0.000057 |
4 | 0.05 | 0.000066 | 0.000067 | 0.000066 | 0.000067 | 0.000067 |
pdf.plot(x='reserve')
<matplotlib.axes._subplots.AxesSubplot at 0x1a1c2a2390>
pdf['mint_slippage'] = (pdf['.1% mint_price']-pdf['spot_price'])/pdf['spot_price']
pdf['withdraw_slippage'] = (pdf['spot_price']-pdf['.1% withdraw_price'])/pdf['spot_price']
pdf['mint_slippage_fee'] = (pdf['.1% mint_price w/fee']-pdf['spot_price'])/pdf['spot_price']
pdf['withdraw_slippage_fee'] = (pdf['spot_price']-pdf['.1% withdraw_price w/fee'])/pdf['spot_price']
pdf[['mint_slippage', 'withdraw_slippage']].hist()
pdf[['mint_slippage_fee', 'withdraw_slippage_fee']].hist()
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1a1c979780>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a1c99fb70>]], dtype=object)
#given V0 and kappa
R = 40
S = supply(R,kappa,V0)
p = spot_price(R,kappa,V0)
#sweep the transaction fraction
TXF = np.logspace(-6, 1, num=5000)
#realized price for withdrawing txf of Xdai
withdraw_price2=[withdrawR(R*txf, R,S, kappa, V0, 0)[1] for txf in TXF]
withdraw_price2_fee=[withdrawR(R*txf, R,S, kappa, V0, phi)[1] for txf in TXF]
#realized price for depositing txf more Xdai into the reserve
mint_price2=[mint(R*txf, R,S, kappa, V0,0)[1] for txf in TXF]
mint_price2_fee=[mint(R*txf, R,S, kappa, V0,phi)[1] for txf in TXF]
/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:43: RuntimeWarning: invalid value encountered in double_scalars
pdf2 = pd.DataFrame({'tx_fraction':TXF, 'spot_price':p*np.ones(len(TXF)), 'mint_price':mint_price2,'withdraw_price':withdraw_price2, 'mint_price_fee':mint_price2_fee,'withdraw_price_fee':withdraw_price2_fee })
pdf2.plot(x='tx_fraction',y=['mint_price','withdraw_price','mint_price_fee','withdraw_price_fee','spot_price'], logx=True)
<matplotlib.axes._subplots.AxesSubplot at 0x1a1caf0be0>
pdf2['bond_to_mint_slippage'] = (pdf2['mint_price']-pdf2['spot_price'])/pdf2['spot_price']
pdf2['burn_to_withdraw_slippage'] = (pdf2['spot_price']-pdf2['withdraw_price'])/pdf2['spot_price']
pdf2['bond_to_mint_slippage_fee'] = (pdf2['mint_price_fee']-pdf2['spot_price'])/pdf2['spot_price']
pdf2['burn_to_withdraw_slippage_fee'] = (pdf2['spot_price']-pdf2['withdraw_price_fee'])/pdf2['spot_price']
ticks=[10**k for k in range(-6,1)]
bound = .026 #need to figure out this analytically in terms of kappa and phi
fig, ax = plt.subplots(figsize=(10,3))
ax.hlines(phi, TXF[0],TXF[-1], linestyle='--')
ax.hlines(1, TXF[0],TXF[-1], linestyle='--')
ax.vlines(bound, ticks[0],ticks[-1], linestyle='--')
ax.vlines(1, ticks[0],ticks[-1], linestyle='--')
pdf2.plot(x='tx_fraction', y = ['bond_to_mint_slippage', 'burn_to_withdraw_slippage'], logx=True, logy=True, ax=ax)
plt.title("Friction for Bond and Withdraw accounting Without Fee")
plt.ylabel("""Percentage Change:
Realized Price
Relative to Spot Price""")
plt.yticks([10**k for k in range(-6,1)])
plt.xlabel("""Transaction size as percent of the asset Burned or Bonded
normalizd units: bonded/Reserve, burned/Supply""")
Text(0.5, 0, 'Transaction size as percent of the asset Burned or Bonded\nnormalizd units: bonded/Reserve, burned/Supply')
fig, ax = plt.subplots(figsize=(10,5))
ax.hlines(phi, TXF[0],TXF[-1], linestyle='--')
ax.hlines(1, TXF[0],TXF[-1], linestyle='--')
ax.vlines(bound, ticks[0],ticks[-1], linestyle='--')
ax.vlines(1, ticks[0],ticks[-1], linestyle='--')
pdf2.plot(x='tx_fraction', y = ['bond_to_mint_slippage', 'burn_to_withdraw_slippage','bond_to_mint_slippage_fee', 'burn_to_withdraw_slippage_fee'], logx=True, logy=True, ax=ax)
plt.title("Friction for Bond and Withdraw accounting With and Without Fee, phi="+str(phi))
plt.ylabel("""Percentage Change:
Realized Price
Relative to Spot Price""")
plt.yticks([10**k for k in range(-6,1)])
plt.xlabel("""Transaction size as percent of the xDAI Reserve
normalizd units: bonded/Reserve, withdraw/Reserve""")
Text(0.5, 0, 'Transaction size as percent of the xDAI Reserve\nnormalizd units: bonded/Reserve, withdraw/Reserve')
fig, ax = plt.subplots(figsize=(10,5))
ax.hlines(phi, TXF[0],TXF[-1], linestyle='--')
ax.hlines(1, TXF[0],TXF[-1], linestyle='--')
ax.vlines(bound, ticks[0],ticks[-1], linestyle='--')
ax.vlines(1, ticks[0],ticks[-1], linestyle='--')
#ax.plot([ticks[0],ticks[-1]],[ticks[0]*phi,ticks[-1]*phi], 'k--')
pdf2.plot(x='tx_fraction', y = ['bond_to_mint_slippage', 'burn_to_withdraw_slippage','bond_to_mint_slippage_fee', 'burn_to_withdraw_slippage_fee'], logx=True, logy=True, ax=ax)
plt.title("Friction for Bond and Withdraw accounting With and Without Fee, phi="+str(phi))
plt.ylabel("""Percentage Change:
Realized Price
Relative to Spot Price""")
plt.yticks(ticks)
plt.xlabel("""Transaction size as percent of the xDAI Reserve
normalizd units: bonded/Reserve, withdraw/Reserve""")
axis = ax.axis()
ax.set(xlim=((1.0-(1.0-phi)**(1.0/kappa))*10, axis[1]), ylim=(phi, axis[3]))
[(0.01, 4.475613870122653), (0.02509430066318874, 10.0)]
fig, ax = plt.subplots(figsize=(10,3))
ax.hlines(phi, TXF[0],TXF[-1], linestyle='--')
ax.hlines(1, TXF[0],TXF[-1], linestyle='--')
ax.vlines(bound, ticks[0],ticks[-1], linestyle='--')
ax.vlines(1, ticks[0],ticks[-1], linestyle='--')
pdf2.plot(x='tx_fraction', y = ['bond_to_mint_slippage_fee', 'burn_to_withdraw_slippage_fee'], logx=True, logy=True, ax=ax)
plt.title("Friction for Bond and Withdraw accounting With Fee, phi="+str(phi))
plt.ylabel("""Percentage Change:
Realized Price
Relative to Spot Price""")
plt.yticks(ticks)
plt.xlabel("""Transaction size as percent of the xDAI Reserve
normalizd units: bonded/Reserve, withdraw/Reserve""")
#axis = ax.axis()
#ax.set(xlim=(bound*.99, axis[1]), ylim=(phi, axis[3]))
Text(0.5, 0, 'Transaction size as percent of the xDAI Reserve\nnormalizd units: bonded/Reserve, withdraw/Reserve')
fig, ax = plt.subplots(figsize=(10,3))
ax.hlines(phi, TXF[0],TXF[-1], linestyle='--')
ax.hlines(1, TXF[0],TXF[-1], linestyle='--')
ax.vlines(bound, ticks[0],ticks[-1], linestyle='--')
ax.vlines(1, ticks[0],ticks[-1], linestyle='--')
pdf2.plot(x='tx_fraction', y = ['bond_to_mint_slippage_fee', 'burn_to_withdraw_slippage_fee'], logx=True, logy=True, ax=ax)
plt.title("Friction for Bond and Withdraw accounting With Fee, phi="+str(phi))
plt.ylabel("""Percentage Change:
Realized Price
Relative to Spot Price""")
plt.yticks(ticks)
plt.xlabel("""Transaction size as percent of the asset Burned or Bonded
normalizd units: bonded/Reserve, burned/Supply""")
axis = ax.axis()
ax.set(xlim=(bound*.99, axis[1]), ylim=(phi, axis[3]))
[(0.01, 4.2613703055703915), (0.02574, 10.0)]
Curvature κ=4 was proposed by Grassroots Economics. This analysis sought to demonstrate this choice and review for any potential red flags. Seems reasonable under the assumptions provided but further analysis is merited to provide context. Such analysis is possible using the cadCAD models also available in this repo.
Fees ϕ if desired are suggested at 0.001 which is 0.1% or 10 basis points. Largely subjective but exist to manage the following considerations: fees should not impose undue strain on users of the system but some fees help reduce spam or noise the system. Fees do not need to be large because the system already has baked in slippage in its invariant preserving logic. If the smart contract is restricted to a whitelist of only institutional actors with additional vetting, fees are not necessary at all. Given the goals and status of the CIC project, no fees are recommended initially, but an extremely narrow whitelist should be used. Access to CICs is provided through community development programs rather than through direct use of the bonding curve smart contracts.