darkie.py 9.2 KB

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