darkie.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. from core.utils import *
  2. from core.strategy import *
  3. class Darkie():
  4. def __init__(self, airdrop, initial_stake=None, vesting=[], hp=False, commit=True, epoch_len=EPOCH_LENGTH, strategy=random_strategy(EPOCH_LENGTH), idx=0):
  5. self.vesting = vesting
  6. self.stake = (Num(airdrop) if hp else airdrop)
  7. self.initial_stake = [self.stake]
  8. self.Sigma = None
  9. self.feedback = None
  10. self.f = None
  11. self.epoch_len=epoch_len # epoch length during which the stake is static
  12. self.strategy = strategy
  13. self.slot = 0
  14. self.won_hist = [] # winning history boolean
  15. self.fees = []
  16. self.tips = [0]
  17. self.idx=idx
  18. self.aprs = [] #milinial aprs. every 1k slots ~ 1day
  19. def clone(self):
  20. return Darkie(self.stake)
  21. """
  22. calculate APY (with compound interest every epoch) every epoch scaled to runningtime
  23. @param rewards: rewards at each epoch
  24. @returns: apy
  25. """
  26. def apy_scaled_to_runningtime(self, rewards):
  27. avg_apy = 0
  28. for idx, reward in enumerate(rewards):
  29. #init_stake = Num(self.initial_stake[idx-1]) if len(self.initial_stake)>=idx else Num(self.initial_stake[-1])
  30. current_epoch_staked_tokens = Num(self.strategy.staked_tokens_ratio[idx-1]) * Num(self.initial_stake[idx-1])
  31. avg_apy += (Num(reward) / current_epoch_staked_tokens) if current_epoch_staked_tokens!=0 else 0
  32. return avg_apy * Num(ONE_YEAR/(self.slot/EPOCH_LENGTH)) if self.slot and self.initial_stake[0]>0 >0 else 0
  33. """
  34. calculate APR every epoch scaled to running time
  35. @returns: apr
  36. """
  37. def apr_scaled_to_runningtime(self):
  38. initial_stake = self.vesting_wrapped_initial_stake()
  39. #assert self.stake >= initial_stake, 'stake: {}, initial_stake: {}, slot: {}, current: {}, previous: {} vesting'.format(self.stake, initial_stake, self.slot, self.current_vesting(), self.prev_vesting())
  40. if self.slot < HEADSTART_AIRDROP:
  41. # during this phase, it's only called at end of epoch
  42. apr_period = self.slot%EPOCH_LENGTH
  43. elif self.slot > MIL_SLOT:
  44. apr_period = self.slot - int(self.slot/MIL_SLOT)*MIL_SLOT
  45. else:
  46. apr_period = self.slot-HEADSTART_AIRDROP
  47. apr_scaled = ((self.stake - initial_stake) / initial_stake) / apr_period if initial_stake>0 and apr_period>0 else 0
  48. apr = apr_scaled * ONE_YEAR if initial_stake > 0 and apr_period>0 and self.slot>0 else 0
  49. #if apr>0 and self.stake-initial_stake>0:
  50. #print("apr: {}, stake: {}, initial_stake: {}".format(apr, self.stake, initial_stake))
  51. return apr
  52. """
  53. add vesting to initial stake
  54. @returns: vesting plus initial stake
  55. """
  56. def vesting_wrapped_initial_stake(self):
  57. #returns vesting stake plus initial stake gained from zero coin headstart during aridrop period
  58. vesting = self.current_vesting()
  59. if self.slot < HEADSTART_AIRDROP:
  60. initial_stake = self.initial_stake[-1]
  61. elif self.slot > MIL_SLOT:
  62. initial_stake = self.initial_stake[int(int(self.slot/MIL_SLOT) * MIL_SLOT/EPOCH_LENGTH)-1]
  63. else:
  64. initial_stake = self.initial_stake[int(HEADSTART_AIRDROP/EPOCH_LENGTH)-1]
  65. return vesting + initial_stake
  66. """
  67. update stake with vesting return every scheduled vesting period
  68. """
  69. def update_vesting(self):
  70. diff = self.vesting_differential()
  71. self.stake += diff
  72. return diff
  73. """
  74. @returns: current epoch vesting
  75. """
  76. def current_vesting(self):
  77. '''
  78. current corresponding slot vesting
  79. '''
  80. vesting_idx = int(self.slot/VESTING_PERIOD)
  81. return self.vesting[vesting_idx] if vesting_idx < len(self.vesting) else 0
  82. """
  83. @returns: previous epoch vesting
  84. """
  85. def prev_vesting(self):
  86. '''
  87. previous corresponding slot vesting
  88. '''
  89. prev_vesting_idx = int((self.slot-1)/VESTING_PERIOD)
  90. return (self.vesting[prev_vesting_idx] if self.slot>0 else self.current_vesting()) if prev_vesting_idx < len(self.vesting) else 0
  91. def vesting_differential(self):
  92. vesting_value = self.current_vesting() - self.prev_vesting()
  93. return vesting_value
  94. def staked_tokens(self):
  95. '''
  96. the ratio of the staked tokens during the epochs
  97. of the total running time
  98. '''
  99. return Num(self.initial_stake[0])*self.staked_tokens_ratio()
  100. """
  101. @returns: average stakeholder's staked ratio from genesis until current slot
  102. """
  103. def staked_tokens_ratio(self):
  104. staked_ratio = Num(sum(self.strategy.staked_tokens_ratio)/len(self.strategy.staked_tokens_ratio))
  105. assert staked_ratio <= 1 and staked_ratio >=0, 'staked_ratio: {}'.format(staked_ratio)
  106. return staked_ratio
  107. def set_sigma_feedback(self, sigma, feedback, f, count, hp=True):
  108. self.Sigma = (Num(sigma) if hp else sigma)
  109. self.feedback = (Num(feedback) if hp else feedback)
  110. self.f = (Num(f) if hp else f)
  111. self.slot = count
  112. """
  113. @param hp: high precision decimal option
  114. play lottery if stakeholder won, update state
  115. """
  116. def run(self, hp=True):
  117. k=N_TERM
  118. def target(tune_parameter, stake):
  119. x = (Num(1) if hp else 1) - (Num(tune_parameter) if hp else tune_parameter)
  120. c = (x.ln() if type(x)==Num else math.log(x))
  121. sigmas = [ c/((self.Sigma+EPSILON)**i) * ( ((L_HP if hp else L)/fact(i)) ) for i in range(1, k+1) ]
  122. headstart = (BASE_L_HP if hp else BASE_L) if self.slot < HEADSTART_AIRDROP else 0
  123. scaled_target = approx_target_in_zk(sigmas, Num(stake)) + headstart
  124. if stake>0:
  125. assert scaled_target>0
  126. return scaled_target
  127. apr = self.apr_scaled_to_runningtime()
  128. if (self.slot+1) % MIL_SLOT == 0:
  129. self.aprs += [apr]
  130. if self.slot % EPOCH_LENGTH ==0 and self.slot > 0:
  131. # staked ratio is added in strategy
  132. self.strategy.set_ratio(self.slot, apr)
  133. # epoch stake is added
  134. self.initial_stake += [self.stake]
  135. T = target(self.f, self.strategy.staked_value(self.stake))
  136. won, y = lottery(T, hp)
  137. self.won_hist += [won]
  138. return y, T
  139. """
  140. update stake upon winning lottery with single lead
  141. """
  142. def update_stake(self, reward):
  143. if self.won_hist[-1]:
  144. self.stake += reward
  145. """
  146. update stake after fork finalization
  147. """
  148. def resync_stake(self, reward):
  149. self.stake += reward
  150. def write(self, idx):
  151. with open('log/darkie'+str(idx)+'.log', 'w+') as f:
  152. buf = 'initial stake:'+','.join([str(i) for i in self.initial_stake])
  153. buf += '\r\n'
  154. buf += '(apr,staked ratio,{}):'.format(self.strategy.type)+','.join(['('+str(apr)+','+str(sr)+')' for sr, apr in zip(self.strategy.staked_tokens_ratio, self.strategy.annual_return)])
  155. buf+='\r\n'
  156. buf += 'apr: {}'.format(self.apr_scaled_to_runningtime())
  157. buf += '\r\n'
  158. buf += 'mil-aprs: {}'.format(','.join([str(apr) for apr in self.aprs]))
  159. buf += '\r\n'
  160. f.write(buf)
  161. """
  162. anonymous contract assumed to be random stream from uniform distribution,
  163. naive emulation of smart contract based transactions with certain computational cost.
  164. @returns: transaction emulated as series of random floats between 0,1
  165. """
  166. def tx(self, last_reward):
  167. tx_size = random.randint(0, MAX_BLOCK_SIZE)
  168. tip = self.tx_tip(tx_size, last_reward)
  169. if self.stake < tip:
  170. return Tx(tx_size, 0, self.idx)
  171. return Tx(tx_size, tip, self.idx)
  172. def tx_tip(self, tx_size, last_reward):
  173. tip = random_tip_strategy()
  174. apr = self.apr_scaled_to_runningtime()
  175. return tip.get_tip(float(last_reward), float(apr), tx_size, self.tips[-1])
  176. """
  177. deduct tip paid to miner plus burned base fee or computational cost.
  178. """
  179. def pay_fee(self, fee):
  180. if fee>0:
  181. self.fees += [fee]
  182. self.stake -= fee
  183. def last_fee(self):
  184. return self.fees[-1] if len(self.fees)>0 else 0
  185. class Tx(object):
  186. def __init__(self, size, tip, idx):
  187. self.tx = [random.random() for _ in range(size)]
  188. self.len = size
  189. self.tip = tip
  190. self.idx=idx
  191. """
  192. anonymous contract assumed to be of random streams from uniform distribution,
  193. it's circuit execution cost it thus random.
  194. naive emulation of transaction smart contract computational cost as a avg of txs sum,
  195. which is random function
  196. @returns: transaction computational cost
  197. """
  198. def cc(self):
  199. return int(sum(self.tx) if len(self.tx)>0 else 0)
  200. def __len__(self):
  201. return len(self.tx)