darkie.py 8.1 KB

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