darkie.py 7.4 KB

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