/ model / chain / model.py
model.py
   1  #!/usr/bin/env python3
   2  
   3  """
   4  model.py: agent-based model of xSD system behavior, against a testnet
   5  """
   6  from subprocess import Popen
   7  import subprocess
   8  import json
   9  import collections
  10  import random
  11  import math
  12  import logging
  13  import time
  14  import sys
  15  import os
  16  import base64
  17  import mmap
  18  from eth_abi import encode_single, decode_single
  19  from web3 import Web3
  20  import datetime
  21  
  22  IS_DEBUG = False
  23  is_try_model_mine = False
  24  max_accounts = 40
  25  block_offset = 19 + max_accounts
  26  tx_pool_latency = 0.25
  27  
  28  DEADLINE_FROM_NOW = 60 * 60 * 24 * 7 * 52
  29  UINT256_MAX = 2**256 - 1
  30  ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
  31  MMAP_FILE = '/tmp/avax-cchain-nonces'
  32  
  33  deploy_data = None
  34  with open("deploy_output.txt", 'r+') as f:
  35      deploy_data = f.read()
  36  
  37  logger = logging.getLogger(__name__)
  38  #provider = Web3.HTTPProvider('http://127.0.0.1:7545/ext/bc/C/rpc', request_kwargs={"timeout": 60*300})
  39  provider = Web3.WebsocketProvider('ws://127.0.0.1:9545/ext/bc/C/ws', websocket_timeout=60*300)
  40  
  41  providerAvax = Web3.HTTPProvider('http://127.0.0.1:9545/ext/bc/C/avax', request_kwargs={"timeout": 60*300})
  42  w3 = Web3(provider)
  43  from web3.middleware import geth_poa_middleware
  44  w3.middleware_onion.inject(geth_poa_middleware, layer=0)
  45  
  46  w3.eth.defaultAccount = w3.eth.accounts[0]
  47  logger.info(w3.eth.blockNumber)
  48  logger.info(w3.clientVersion)
  49  #sys.exit()
  50  
  51  TPRO = {
  52      "addr": '',
  53      "deploy_slug": "timeProviderAddress is at: "
  54  }
  55  
  56  EXCHG = {
  57      "addr": '',
  58      "decimals": 18,
  59      "deploy_slug": "OptionsExchangeAddress is at: "
  60  }
  61  
  62  PROPSMNGERHELPER = {
  63      "addr":"0x04AbCcEEd429062b0b145e85a91ee2D529b2840A"
  64  }
  65  
  66  PROPSWRPR = {
  67      "addr": Web3.toChecksumAddress("0xbe9a38abe9bb3c28b164f1b0f9aeca9591cb8fb1")
  68  }
  69  
  70  PROPSMNGER = {
  71      "addr": '0x6fe9afFFc3ffa4D59638486bbD029d0138D6E0af',
  72      "deploy_slug": "OptionsExchangeAddress is at: "
  73  } 
  74  
  75  CREDPRO = {
  76      "addr": '',
  77      "deploy_slug": "CreditProviderAddress is at: "
  78  }
  79  
  80  STG = {
  81      "addr": '',
  82      "deploy_slug": "ProtocolSettingsAddress is at: "
  83  }
  84  
  85  DPLY = {
  86      "addr": '',
  87      "deploy_slug": "Deployer4 is at: "
  88  }
  89  
  90  # USE FROM XSD SIMULATION
  91  USDT = {
  92    "addr": '0x351D75454225010b2d2EeBd0E96762291661CDcB',
  93    "decimals": 6,
  94    "symbol": 'USDT',
  95  }
  96  
  97  LLPF = {
  98      "addr": '',
  99      "deploy_slug": "LinearLiquidityPoolFactoryAddress is at: "
 100  }
 101  
 102  BTCUSDAgg = {
 103    "addr": '',
 104    "decimals": 8,
 105    "symbol": 'BTCUSD',
 106    "deploy_slug": "BTCUSDAgg is at: "
 107  }
 108  
 109  BTCUSDc = {
 110      "addr": '',
 111      "decimals": 18,
 112      "symbol": 'BTCUSDc',
 113      "deploy_slug": "BTCUSDMockFeed is at: "
 114  }
 115  
 116  for contract in [BTCUSDc, BTCUSDAgg, LLPF, STG, CREDPRO, EXCHG, TPRO]:
 117      logger.info(contract["deploy_slug"])
 118      contract["addr"] = deploy_data.split(contract["deploy_slug"])[1].split('\n')[0]
 119      logger.info('\t'+contract["addr"])
 120  
 121  
 122  # token (from Deploy Root on testnet)
 123  xSD = {
 124    "addr": '',
 125    "decimals": 18,
 126    "symbol": 'xSD',
 127  }
 128  
 129  AggregatorV3MockContract = json.loads(open('./build/contracts/AggregatorV3Mock.json', 'r+').read())
 130  ChainlinkFeedContract = json.loads(open('./build/contracts/ChainlinkFeed.json', 'r+').read())
 131  CreditProviderContract = json.loads(open('./build/contracts/CreditProvider.json', 'r+').read())
 132  OptionsExchangeContract = json.loads(open('./build/contracts/OptionsExchange.json', 'r+').read())
 133  ProposalManagerContract = json.loads(open('./build/contracts/ProposalsManager.json', 'r+').read())
 134  
 135  ProposalManagerHelperContract = json.loads(open('./build/contracts/PoolManagementProposal.json', 'r+').read())
 136  ProposalWrapperContract = json.loads(open('./build/contracts/ProposalWrapper.json', 'r+').read())
 137   
 138  USDTContract = json.loads(open('./build/contracts/TestnetUSDT.json', 'r+').read())
 139  OptionTokenContract = json.loads(open('./build/contracts/OptionToken.json', 'r+').read())
 140  ProtocolSettingsContract = json.loads(open('./build/contracts/ProtocolSettings.json', 'r+').read())
 141  LinearLiquidityPoolContract = json.loads(open('./build/contracts/LinearLiquidityPool.json', 'r+').read())
 142  LinearLiquidityPoolFactoryContract = json.loads(open('./build/contracts/LinearLiquidityPoolFactory.json', 'r+').read())
 143  ERC20StableCoinContract = json.loads(open('./build/contracts/ERC20.json', 'r+').read())
 144  TimeProviderMockContract = json.loads(open('./build/contracts/TimeProviderMock.json', 'r+').read())
 145  
 146  
 147  def get_addr_from_contract(contract):
 148      return contract["networks"][str(sorted(map(int,contract["networks"].keys()))[0])]["address"]
 149  
 150  avax_cchain_nonces = None
 151  mm = None
 152  
 153  def lock_nonce(agent):
 154      global mm
 155      # DECODE START
 156      if not mm:
 157          mm = mmap.mmap(avax_cchain_nonces.fileno(), 0)
 158  
 159      mm.seek(0)
 160      raw_data_cov = mm.read().decode('utf8')
 161      nonce_data = json.loads(raw_data_cov)
 162  
 163      nonce_data['locked'] = '1'
 164      out_data = bytes(json.dumps(nonce_data), 'utf8')
 165      mm[0:] = out_data
 166  
 167  def get_nonce(agent):
 168      global mm
 169      # DECODE START
 170      if not mm:
 171          mm = mmap.mmap(avax_cchain_nonces.fileno(), 0)
 172  
 173      mm.seek(0)
 174      raw_data_cov = mm.read().decode('utf8')
 175      nonce_data = json.loads(raw_data_cov)
 176      current_block = int(w3.eth.get_block('latest')["number"])
 177  
 178      while nonce_data['locked'] == '1':
 179          mm.seek(0)
 180          raw_data_cov = mm.read().decode('utf8')
 181          nonce_data = json.loads(raw_data_cov)
 182          mm.seek(0)
 183          continue
 184  
 185      # locked == '1', unlocked == '0'
 186      
 187      nonce_data[agent.address]["seen_block"] = decode_single('uint256', base64.b64decode(nonce_data[agent.address]["seen_block"]))
 188      nonce_data[agent.address]["next_tx_count"] = decode_single('uint256', base64.b64decode(nonce_data[agent.address]["next_tx_count"]))
 189      # DECODE END
 190  
 191      if current_block != nonce_data[agent.address]["seen_block"]:
 192          if (nonce_data[agent.address]["seen_block"] == 0):
 193              nonce_data[agent.address]["seen_block"] = current_block
 194              nonce_data[agent.address]["next_tx_count"] = agent.next_tx_count
 195          else:
 196              nonce_data[agent.address]["seen_block"] = current_block
 197              nonce_data[agent.address]["next_tx_count"] = agent.next_tx_count
 198              nonce_data[agent.address]["next_tx_count"] += 1
 199              agent.next_tx_count = nonce_data[agent.address]["next_tx_count"]
 200      else:
 201          nonce_data[agent.address]["next_tx_count"] = agent.next_tx_count
 202          nonce_data[agent.address]["next_tx_count"] += 1
 203          agent.next_tx_count = nonce_data[agent.address]["next_tx_count"]
 204  
 205      # ENCODE START
 206      nonce_data[agent.address]["seen_block"] = base64.b64encode(encode_single('uint256', nonce_data[agent.address]["seen_block"])).decode('ascii')
 207      nonce_data[agent.address]["next_tx_count"] = base64.b64encode(encode_single('uint256', nonce_data[agent.address]["next_tx_count"])).decode('ascii')
 208      
 209      # ENCODE END
 210      return agent.next_tx_count
 211  
 212  def unlock_nonce(agent):
 213      global mm
 214      # DECODE START
 215      if not mm:
 216          mm = mmap.mmap(avax_cchain_nonces.fileno(), 0)
 217  
 218      mm.seek(0)
 219      raw_data_cov = mm.read().decode('utf8')
 220      nonce_data = json.loads(raw_data_cov)
 221  
 222      nonce_data['locked'] = '0'
 223      out_data = bytes(json.dumps(nonce_data), 'utf8')
 224      mm[:] = out_data
 225  
 226  def transaction_helper(agent, prepped_function_call, gas):
 227      tx_hash = None
 228      nonce = get_nonce(agent)
 229      while tx_hash is None:
 230          try:
 231              agent.next_tx_count = nonce
 232              lock_nonce(agent)
 233              tx_hash = prepped_function_call.transact({
 234                  'chainId': 43112,
 235                  'nonce': nonce,
 236                  'from' : getattr(agent, 'address', agent),
 237                  'gas': gas,
 238                  'gasPrice': Web3.toWei(225, 'gwei'),
 239              })
 240              unlock_nonce(agent)
 241          except Exception as inst:
 242              err_str = str(inst)
 243              if 'nonce too low' in err_str:
 244                  # increment tx_hash
 245                  unlock_nonce(agent)
 246                  nonce +=1
 247              elif 'replacement transaction underpriced' in err_str:
 248                  # increment tx_hash
 249                  unlock_nonce(agent)
 250                  nonce +=1
 251              else:
 252                  unlock_nonce(agent)
 253                  nonce +=1
 254                  print(inst)
 255      return tx_hash
 256  
 257  def pretty(d, indent=0):
 258     """
 259     Pretty-print a value.
 260     """
 261     if not isinstance(d, dict) and not isinstance(d, list):
 262         print('\t' * (indent+1) + str(d))
 263     else: 
 264         for key, value in d.items():
 265            print('\t' * indent + str(key))
 266            if isinstance(value, dict):
 267               pretty(value, indent+1)
 268            elif isinstance(value, list):
 269              for v in value:
 270                  pretty(v, indent+1)
 271            else:
 272               print('\t' * (indent+1) + str(value))
 273  
 274  def portion_dedusted(total, fraction):
 275      """
 276      Compute the amount of an asset to use, given that you have
 277      total and you don't want to leave behind dust.
 278      """
 279      
 280      if total - (fraction * total) <= 1:
 281          return total
 282      else:
 283          return fraction * total
 284  
 285  def defaultdict_from_dict(d):
 286      #nd = lambda: collections.defaultdict(nd)
 287      ni = collections.defaultdict(set)
 288      ni.update(d)
 289      return ni
 290  
 291  def execute_cmd(cmd):
 292      try:
 293          proc = Popen(cmd, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
 294          proc_data = proc.communicate()[0]
 295          return proc_data
 296      
 297      except Exception as inst:
 298          print (inst)
 299          data = inst
 300      return data
 301  
 302  # Because token balances need to be accuaate to the atomic unit, we can't store
 303  # them as floats. Otherwise we might turn our float back into a token balance
 304  # different from the balance we actually had, and try to spend more than we
 305  # have. But also, it's ugly to throw around total counts of atomic units. So we
 306  # use this class that represents a fixed-point token balance.
 307  class Balance:
 308      def __init__(self, wei=0, decimals=0):
 309          self._wei = int(wei)
 310          self._decimals = int(decimals)
 311          
 312      def clone(self):
 313          """
 314          Make a deep copy so += and -= on us won't infect the copy.
 315          """
 316          return Balance(self._wei, self._decimals)
 317          
 318      def to_decimals(self, new_decimals):
 319          """
 320          Get a similar balance with a different number of decimals.
 321          """
 322          
 323          return Balance(self._wei * 10**new_decimals // 10**self._decimals, new_decimals)
 324          
 325      @classmethod
 326      def from_tokens(cls, n, decimals=0):
 327          return cls(n * 10**decimals, decimals)
 328  
 329      def __add__(self, other):
 330          if isinstance(other, Balance):
 331              if other._decimals != self._decimals:
 332                  raise ValueError("Cannot add balances with different decimals: {}, {}", self, other)
 333              return Balance(self._wei + other._wei, self._decimals)
 334          else:
 335              return Balance(self._wei + other * 10**self._decimals, self._decimals)
 336  
 337      def __iadd__(self, other):
 338          if isinstance(other, Balance):
 339              if other._decimals != self._decimals:
 340                  raise ValueError("Cannot add balances with different decimals: {}, {}", self, other)
 341              self._wei += other._wei
 342          else:
 343              self._wei += other * 10**self._decimals
 344          return self
 345          
 346      def __radd__(self, other):
 347          return self + other
 348          
 349      def __sub__(self, other):
 350          if isinstance(other, Balance):
 351              if other._decimals != self._decimals:
 352                  raise ValueError("Cannot subtract balances with different decimals: {}, {}", self, other)
 353              return Balance(self._wei - other._wei, self._decimals)
 354          else:
 355              return Balance(self._wei - other * 10**self._decimals, self._decimals)
 356  
 357      def __isub__(self, other):
 358          if isinstance(other, Balance):
 359              if other._decimals != self._decimals:
 360                  raise ValueError("Cannot subtract balances with different decimals: {}, {}", self, other)
 361              self._wei -= other._wei
 362          else:
 363              self._wei -= other * 10**self._decimals
 364          return self
 365          
 366      def __rsub__(self, other):
 367          return Balance(other * 10**self._decimals, self._decimals) - self
 368          
 369      def __mul__(self, other):
 370          if isinstance(other, Balance):
 371              raise TypeError("Cannot multiply two balances")
 372          return Balance(self._wei * other, self._decimals)
 373          
 374      def __imul__(self, other):
 375          if isinstance(other, Balance):
 376              raise TypeError("Cannot multiply two balances")
 377          self._wei = int(self._wei * other)
 378          
 379      def __rmul__(self, other):
 380          return self * other
 381          
 382      def __truediv__(self, other):
 383          if isinstance(other, Balance):
 384              raise TypeError("Cannot divide two balances")
 385          return Balance(self._wei // other, self._decimals)
 386          
 387      def __itruediv__(self, other):
 388          if isinstance(other, Balance):
 389              raise TypeError("Cannot divide two balances")
 390          self._wei = int(self._wei // other)
 391          
 392      # No rtruediv because dividing by a balance is silly.
 393      
 394      # Todo: floordiv? divmod?
 395      
 396      def __lt__(self, other):
 397          if isinstance(other, Balance):
 398              if other._decimals != self._decimals:
 399                  raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other)
 400              return self._wei < other._wei
 401          else:
 402              return float(self) < other
 403              
 404      def __le__(self, other):
 405          if isinstance(other, Balance):
 406              if other._decimals != self._decimals:
 407                  raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other)
 408              return self._wei <= other._wei
 409          else:
 410              return float(self) <= other
 411              
 412      def __gt__(self, other):
 413          if isinstance(other, Balance):
 414              if other._decimals != self._decimals:
 415                  raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other)
 416              return self._wei > other._wei
 417          else:
 418              return float(self) > other
 419              
 420      def __ge__(self, other):
 421          if isinstance(other, Balance):
 422              if other._decimals != self._decimals:
 423                  raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other)
 424              return self._wei >= other._wei
 425          else:
 426              return float(self) >= other
 427              
 428      def __eq__(self, other):
 429          if isinstance(other, Balance):
 430              if other._decimals != self._decimals:
 431                  raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other)
 432              return self._wei == other._wei
 433          else:
 434              return float(self) == other
 435              
 436      def __ne__(self, other):
 437          if isinstance(other, Balance):
 438              if other._decimals != self._decimals:
 439                  raise ValueError("Cannot compare balances with different decimals: {}, {}", self, other)
 440              return self._wei != other._wei
 441          else:
 442              return float(self) != other
 443  
 444      def __str__(self):
 445          base = 10**self._decimals
 446          ipart = self._wei // base
 447          fpart = self._wei - base * ipart
 448          return ('{}.{:0' + str(self._decimals) + 'd}').format(ipart, fpart)
 449  
 450      def __repr__(self):
 451          return 'Balance({}, {})'.format(self._wei, self._decimals)
 452          
 453      def __float__(self):
 454          return self._wei / 10**self._decimals
 455  
 456      def __round__(self):
 457          return Balance(int(math.floor(self._wei / 10**self._decimals)) * 10**self._decimals, self._decimals)
 458          
 459      def __format__(self, s):
 460          if s == '':
 461              return str(self)
 462          return float(self).__format__(s)
 463          
 464      def to_wei(self):
 465          return self._wei
 466          
 467      def decimals(self):
 468          return self._decimals
 469  
 470  class TokenProxy:
 471      """
 472      A proxy for an ERC20 token. Monitors events, processes them when update()
 473      is called, and fulfils balance requests from memory.
 474      """
 475      
 476      def __init__(self, contract):
 477          """
 478          Set up a proxy around a Web3py contract object that implements ERC20.
 479          """
 480          
 481          self.__contract = contract
 482          self.__transfer_filter = self.__contract.events.Transfer.createFilter(fromBlock='latest')
 483          # This maps from string address to Balance balance
 484          self.__balances = {}
 485          # This records who we approved for who
 486          self.__approved_file = "{}-{}.json".format(str(contract.address), 'approvals')
 487  
 488          if not os.path.exists(self.__approved_file):
 489              f = open(self.__approved_file, 'w+')
 490              f.write('{}')
 491              f.close()
 492              tmp_file_data = {}
 493          else:
 494              data = open(self.__approved_file, 'r+').read()
 495              tmp_file_data = {} if len(data) == 0 else json.loads(data)
 496          self.__approved = tmp_file_data
 497          
 498          # Load initial parameters from the chain.
 499          # Assumes no events are happening to change the supply while we are doing this.
 500          self.__decimals = self.__contract.functions.decimals().call()
 501          self.__symbol = self.__contract.functions.symbol().call()
 502          self.__supply = Balance(self.__contract.functions.totalSupply().call(), self.__decimals)
 503  
 504      # Expose some properties to make us easy to use in place of the contract
 505          
 506      @property
 507      def decimals(self):
 508          return self.__decimals
 509          
 510      @property
 511      def symbol(self):
 512          return self.__symbol
 513          
 514      @property
 515      def totalSupply(self):
 516          return self.__supply
 517          
 518      @property
 519      def address(self):
 520          return self.__contract.address
 521          
 522      @property
 523      def contract(self):
 524          return self.__contract
 525          
 526      def update(self, is_init_agents=[]):
 527          """
 528          Process pending events and update state to match chain.
 529          Assumes no transactions are still in flight.
 530          """
 531          
 532          # These addresses need to be polled because we have no balance from
 533          # before all these events.
 534          new_addresses = set()
 535          try:
 536              for transfer in self.__transfer_filter.get_new_entries():
 537                  # For every transfer event since we last updated...
 538                  
 539                  # Each loooks something like:
 540                  # AttributeDict({'args': AttributeDict({'from': '0x0000000000000000000000000000000000000000', 
 541                  # 'to': '0x20042A784Bf0743fcD81136422e12297f52959a0', 'value': 19060347313}), 
 542                  # 'event': 'Transfer', 'logIndex': 0, 'transactionIndex': 0,
 543                  # 'transactionHash': HexBytes('0xa6f4ca515b28301b224f24b7ee14b8911d783e2bf965dbcda5784b4296c84c23'), 
 544                  # 'address': '0xa2Ff73731Ee46aBb6766087CE33216aee5a30d5e', 
 545                  # 'blockHash': HexBytes('0xb5ffd135318581fcd5cd2463cf3eef8aaf238bef545e460c284ad6283928ed08'),
 546                  # 'blockNumber': 17})
 547                  args = transfer['args']
 548                  
 549                  moved = Balance(args['value'], self.__decimals)
 550                  if args['from'] in self.__balances:
 551                      self.__balances[args['from']] -= moved
 552                  elif args['from'] == ZERO_ADDRESS:
 553                      # This is a mint
 554                      self.__supply += moved
 555                  else:
 556                      new_addresses.add(args['from'])
 557                  if args['to'] in self.__balances:
 558                      self.__balances[args['to']] += moved
 559                  elif args['to'] == ZERO_ADDRESS:
 560                      # This is a burn
 561                      self.__supply -= moved
 562                  else:
 563                      new_addresses.add(args['to'])
 564          except:
 565              pass
 566          
 567          for address in new_addresses:
 568              # TODO: can we get a return value and a correct-as-of block in the same call?
 569              self.__balances[address] = Balance(self.__contract.functions.balanceOf(address).call(), self.__decimals)
 570  
 571          if is_init_agents:
 572              for agent in is_init_agents:
 573                  # TODO: can we get a return value and a correct-as-of block in the same call?
 574                  self.__balances[agent.address] = Balance(self.__contract.functions.balanceOf(agent.address).call(), self.__decimals)
 575  
 576              
 577      def __getitem__(self, address):
 578          """
 579          Get the balance of the given address as a Balance, with the given number of decimals.
 580          
 581          Address can be a string or any object with an .address field.
 582          """
 583          
 584          address = getattr(address, 'address', address)
 585          
 586          if address not in self.__balances:
 587              # Don't actually cache here; wait for a transfer.
 588              # Transactions may still be in flight
 589              return Balance(self.__contract.functions.balanceOf(address).call(), self.__decimals)
 590          else:
 591              # Clone the stored balance so it doesn't get modified and upset the user
 592              return self.__balances[address].clone()
 593              
 594      def ensure_approved(self, owner, spender):
 595          """
 596          Approve the given spender to spend all the owner's tokens on their behalf.
 597          
 598          Owner and spender may be addresses or things with addresses.
 599          """
 600          spender = getattr(spender, 'address', spender)
 601          
 602          if (getattr(owner, 'address', owner) not in self.__approved) or (spender not in self.__approved[getattr(owner, 'address', owner)]):
 603              # Issue an approval
 604              #logger.info('WAITING FOR APPROVAL {} for {}'.format(getattr(owner, 'address', owner), spender))
 605              tx_hash = transaction_helper(
 606                  owner,
 607                  self.__contract.functions.approve(spender, UINT256_MAX),
 608                  500000
 609              )
 610              receipt = w3.eth.waitForTransactionReceipt(tx_hash, poll_latency=tx_pool_latency)
 611              #logger.info('APPROVED')
 612              if getattr(owner, 'address', owner) not in self.__approved:
 613                  self.__approved[getattr(owner, 'address', owner)] = {spender: 1}
 614              else:
 615                  self.__approved[getattr(owner, 'address', owner)][spender] = 1
 616  
 617              open(self.__approved_file, 'w+').write(json.dumps(self.__approved))
 618              
 619      def from_wei(self, wei):
 620          """
 621          Convert a number of wei (possibly a float) into a Balance with the
 622          right number of decimals.
 623          """
 624          
 625          return Balance(wei, self.__decimals)
 626          
 627      def from_tokens(self, tokens):
 628          """
 629          Convert a number of token units (possibly a float) into a Balance with
 630          the right number of decimals.
 631          """
 632          
 633          return Balance.from_tokens(tokens, self.__decimals)
 634          
 635  class Agent:
 636      """
 637      Represents an agent. Tracks all the agent's balances.
 638      """
 639      
 640      def __init__(self, linear_liquidity_pool, options_exchange, credit_provider, xsd_token, usdt_token, **kwargs):
 641   
 642          # xSD TokenProxy
 643          self.xsd_token = xsd_token
 644          # USDT TokenProxy 
 645          self.usdt_token = usdt_token
 646  
 647          self.option_tokens = []
 648  
 649  
 650          self.credit_provider = credit_provider
 651          
 652          # avax balance
 653          self.avax = kwargs.get("starting_avax", Balance(0, 18))
 654          
 655          # What's our max faith in the system in USDT?
 656          self.max_faith = kwargs.get("max_faith", 0.0)
 657          # And our min faith
 658          self.min_faith = kwargs.get("min_faith", 0.0)
 659          # Should we even use faith?
 660          self.use_faith = kwargs.get("use_faith", True)
 661  
 662          # add wallet addr
 663          self.address = kwargs.get("wallet_address", '0x0000000000000000000000000000000000000000')
 664  
 665          # Linear Liquidity Pool + Proxy
 666          self.linear_liquidity_pool = linear_liquidity_pool
 667          # Options Exchange
 668          self.options_exchange = options_exchange
 669  
 670          # keeps track of latest block seen for nonce tracking/tx
 671          self.next_tx_count = w3.eth.getTransactionCount(self.address, block_identifier=int(w3.eth.get_block('latest')["number"]))
 672  
 673          if kwargs.get("is_mint", False):
 674              # need to mint USDT to the wallets for each agent
 675              start_usdt_formatted = kwargs.get("starting_usdt", Balance(0, USDT["decimals"]))
 676              tx_hash = transaction_helper(
 677                  self,
 678                  self.usdt_token.contract.functions.mint(
 679                      self.address, start_usdt_formatted.to_wei()
 680                  ),
 681                  500000
 682              )
 683              time.sleep(1.1)
 684              w3.eth.waitForTransactionReceipt(tx_hash, poll_latency=tx_pool_latency)
 685          
 686      @property
 687      def xsd(self):
 688          """
 689          Get the current balance in USDT from the TokenProxy.
 690          """
 691          return self.xsd_token[self] if self.xsd_token else 0
 692      
 693      @property
 694      def usdt(self):
 695          """
 696          Get the current balance in USDT from the TokenProxy.
 697          """
 698          return self.usdt_token[self]
 699  
 700      @property
 701      def total_written(self):
 702          return Balance(self.options_exchange.get_total_owner_written(self), EXCHG['decimals'])
 703  
 704      @property
 705      def total_holding(self):
 706          return Balance(self.options_exchange.get_total_owner_holding(self), EXCHG['decimals'])
 707  
 708      @property
 709      def lp(self):
 710          """
 711          Get the current balance in Linear Liquidity Pool LP Shares from the TokenProxy.
 712          """
 713          return self.linear_liquidity_pool[self]
 714  
 715      @property
 716      def short_collateral_exposure(self):
 717          """
 718          Get the short collateral balance for agent
 719          """
 720          return self.credit_provider.get_short_collateral_exposure(self)
 721      
 722      def __str__(self):
 723          """
 724          Turn into a readable string summary.
 725          """
 726          return "Agent(xSD={:.2f}, usdt={:.2f}, avax={}, lp={}, total_written={}, total_holding={}, short_collateral_exposure={:.2f})".format(
 727              self.xsd, self.usdt, self.avax, self.lp, self.total_written, self.total_holding, self.short_collateral_exposure)
 728  
 729          
 730      def get_strategy(self, current_timestamp):
 731          """
 732          Get weights, as a dict from action to float, as a function of the price.
 733          """
 734          
 735          strategy = collections.defaultdict(lambda: 1.0)
 736  
 737          strategy["buy"] = 1.0
 738          strategy["sell"] = 10.0
 739          strategy["write"] = 1.0
 740          strategy["deposit_exchange"] = 1.0
 741          strategy["deposit_pool"] = 1.0
 742          strategy["add_symbol"] = 1.0
 743          strategy["create_symbol"] = 1.0
 744          
 745         
 746          if self.use_faith:
 747              # Vary our strategy based on  ... ?
 748              pass
 749          
 750          return strategy
 751          
 752      def get_faith(self, current_timestamp, price, total_supply):
 753          """
 754          Get the total faith in xSD that this agent has, in USDT.
 755          
 756          If the market cap is over the faith, the agent thinks the system is
 757          over-valued. If the market cap is under the faith, the agent thinks the
 758          system is under-valued.
 759          """
 760          
 761          # TODO: model the real economy as bidding on utility in
 762          # mutually-beneficial exchanges conducted in xSD, for which a velocity
 763          # is needed, instead of an abstract faith?
 764          
 765          # TODO: different faith for different people
 766          
 767          center_faith = (self.max_faith + self.min_faith) / 2
 768          swing_faith = (self.max_faith - self.min_faith) / 2
 769          faith = center_faith + swing_faith * math.sin(current_timestamp * (2 * math.pi / 5000000))
 770          
 771          return faith
 772          
 773  class OptionsExchange:
 774      def __init__(self, contract, usdt_token, btcusd_chainlink_feed, **kwargs):
 775          self.contract = contract
 776          self.usdt_token = usdt_token
 777          self.btcusd_chainlink_feed = btcusd_chainlink_feed
 778          self.option_tokens = {}
 779  
 780      def balance(self, agent):
 781          bal = self.contract.caller({'from' : agent.address, 'gas': 100000}).balanceOf(agent.address)
 782          return Balance(bal, EXCHG['decimals'])
 783  
 784      def resolve_token(self, agent, symbol):
 785          '''
 786              resolveToken(symbol)
 787          '''
 788          option_token_address = None
 789  
 790          try:
 791              option_token_address = self.contract.caller({'from' : agent.address, 'gas': 100000}).resolveToken(symbol)
 792  
 793  
 794          except Exception as inst:
 795              if "token not found" in str(inst):
 796                  pass
 797              else:
 798                  raise(inst)
 799          return option_token_address
 800  
 801      def deposit_exchange(self, agent, amount):
 802          '''
 803              ERC20 stablecoin = ERC20(0x123...);
 804              OptionsExchange exchange = OptionsExchange(0xABC...);
 805  
 806              address to = 0x456...;
 807              uint value = 100e18;
 808              stablecoin.approve(address(exchange), value);
 809              exchange.depositTokens(to, address(stablecoin), value);
 810          '''
 811          self.usdt_token.ensure_approved(agent, self.contract.address)
 812          tx = transaction_helper(
 813              agent,
 814              self.contract.functions.depositTokens(
 815                  agent.address,
 816                  self.usdt_token.address,
 817                  amount.to_wei()
 818              ),
 819              500000
 820          )
 821          return tx
 822  
 823      def withdraw(self, agent, amount):
 824          '''
 825              uint value = 50e18;
 826              exchange.withdrawTokens(value);
 827          '''
 828          tx = transaction_helper(
 829              agent,
 830              self.contract.functions.withdrawTokens(
 831                  amount.to_wei()
 832              ),
 833              8000000
 834          )
 835          return tx
 836  
 837      def calc_collateral(self, agent, feed_address, option_type, amount, strike_price, maturity):
 838          '''
 839  
 840          address eth_usd_feed = address(0x987...);
 841          uint volumeBase = 1e18;
 842          uint strikePrice = 1300e18;
 843          uint maturity = now + 30 days;
 844  
 845          uint collateral = exchange.calcCollateral(
 846              feed_address, 
 847              10 * volumeBase, 
 848              OptionsExchange.OptionType.CALL, 
 849              strikePrice, 
 850              maturity
 851          );
 852          '''
 853  
 854          cc = self.contract.caller({'from' : agent.address, 'gas': 8000000}).calcCollateral(
 855              feed_address,
 856              Balance.from_tokens(amount, EXCHG['decimals']).to_wei(),
 857              0 if option_type == 'CALL' else 1,
 858              strike_price,
 859              maturity
 860          )
 861          return Balance(cc, EXCHG['decimals'])
 862  
 863      def write(self, agent, feed_address, option_type, amount, strike_price, maturity):
 864          '''
 865              address tkAddr = exchange.writeOptions(
 866                  eth_usd_feed, 
 867                  10 * volumeBase, 
 868                  OptionsExchange.OptionType.CALL, 
 869                  strikePrice, 
 870                  maturity,
 871                  holder
 872              );
 873          '''
 874          tx = transaction_helper(
 875              agent,
 876              self.contract.functions.writeOptions(
 877                  feed_address,
 878                  Balance.from_tokens(amount, EXCHG['decimals']).to_wei(),
 879                  0 if option_type == 'CALL' else 1,
 880                  strike_price,
 881                  maturity,
 882                  agent.address
 883              ),
 884              8000000
 885          )
 886          return tx
 887  
 888      def create_symbol(self, agent, symbol, btcusd_chainlink_feed):
 889          tx = transaction_helper(
 890              agent,
 891              self.contract.functions.createSymbol(
 892                  symbol,
 893                  btcusd_chainlink_feed.address
 894              ),
 895              8000000
 896          )
 897          return tx
 898  
 899  
 900      def burn_token(self, agent, option_token_address, token_amount):
 901          '''
 902          uint amount = token_amount * volumeBase;
 903          token.burn(amount);
 904          '''
 905          option_token = w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address)
 906          tx = transaction_helper(
 907              agent,
 908              option_token.functions.burn(
 909                  agent.address,
 910                  token_amount.to_wei()
 911              ),
 912              8000000
 913          )
 914          return tx
 915  
 916      def redeem_token(self, agent, option_token_address):
 917          option_token = w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address)
 918          tx = transaction_helper(
 919              agent,
 920              option_token.functions.redeem(
 921                  agent.address
 922              ),
 923              8000000
 924          )
 925          return tx
 926  
 927      def liquidate(self, agent, options_address, owner_address):
 928          '''
 929              exchange.liquidateOptions()
 930          '''
 931          tx = transaction_helper(
 932              agent,
 933              self.contract.functions.liquidateOptions(
 934                  options_address,
 935                  owner_address
 936              ),
 937              8000000
 938          )
 939          return tx
 940  
 941      def get_total_short_collateral_exposure(self, agent):
 942  
 943          '''
 944  
 945          NEED TO FIGURE OUT A BETTER WAY TO TRACK THIS
 946              return self.contract.caller({'from' : agent.address, 'gas': 100000}).getOptionsExchangeTotalExposure()
 947          '''
 948          return 0
 949  
 950      def get_total_written(self, agent):
 951          '''
 952              - loop over all options tokens and get (for total written)
 953                  - totalWrittenVolume()
 954          '''
 955          tw = 0
 956          for k, ot in self.option_tokens.items():
 957              tw += ot.contract.caller({'from' : agent.address, 'gas': 100000}).totalWrittenVolume()
 958          return tw
 959  
 960      def get_total_holding(self, agent):
 961          '''
 962              - loop over all options tokens and get (for total holding)
 963                  - totalSupply()
 964          '''
 965          th = 0
 966          for k, ot in self.option_tokens.items():
 967              th += ot.contract.caller({'from' : agent.address, 'gas': 100000}).totalSupply()
 968          return th
 969  
 970      def get_total_owner_written(self, agent):
 971          '''
 972              - loop over all options tokens and get (for written)
 973                  - writtenVolume(address owner)
 974          '''
 975          ow = 0
 976          for k, ot in self.option_tokens.items():
 977              ow += ot.contract.caller({'from' : agent.address, 'gas': 8000000}).writtenVolume(agent.address)
 978          return ow
 979  
 980      def get_total_owner_holding(self, agent):
 981          '''
 982              - loop over all options tokens and get (for holding)
 983                  - balanceOf(address owner)
 984          '''
 985          oh = 0
 986          for k, ot in self.option_tokens.items():
 987              oh += ot.contract.caller({'from' : agent.address, 'gas': 8000000}).balanceOf(agent.address)
 988          return oh
 989  
 990      def calc_collateral_surplus(self, checker, agent):
 991          cs = self.contract.caller({'from' : checker.address, 'gas': 8000000}).calcSurplus(agent.address)
 992          return Balance(cs, EXCHG['decimals'])
 993  
 994      def prefetch_daily(self, agent, current_round_id, iv_bin_window):
 995          '''
 996  
 997              First call prefetchDailyPrice passing in the "roundId" of the latest sample you appended to your mock, corresponding to the underlying price for the new day
 998              Then call prefetchDailyVolatility passing in the volatility period defined in the ProtocolSettings contract (defaults to 90 days)
 999              Maybe twap can be updated daily?
1000          '''
1001          txr = transaction_helper(
1002              agent,
1003              self.btcusd_chainlink_feed.functions.prefetchDailyPrice(
1004                  current_round_id
1005              ),
1006              8000000
1007          )
1008          txr_recp = w3.eth.waitForTransactionReceipt(txr, poll_latency=tx_pool_latency, timeout=600)
1009          print("prefetchDailyPrice", txr_recp)
1010          
1011          txv = transaction_helper(
1012              agent,
1013              self.btcusd_chainlink_feed.functions.prefetchDailyVolatility(
1014                  iv_bin_window
1015              ),
1016              8000000
1017          )
1018          txv_recp = w3.eth.waitForTransactionReceipt(txv, poll_latency=tx_pool_latency, timeout=600)
1019          print("prefetchDailyVolatility", txr_recp)
1020  
1021      def prefetch_sample(self, agent):
1022          txr = transaction_helper(
1023              agent,
1024              self.btcusd_chainlink_feed.functions.prefetchSample(),
1025              8000000
1026          )
1027          txv_recp = w3.eth.waitForTransactionReceipt(txr, poll_latency=tx_pool_latency, timeout=600)
1028  
1029  class CreditProvider:
1030      def __init__(self, contract, **kwargs):
1031          self.contract = contract
1032  
1033      def get_total_balance(self, agent):
1034          '''
1035              Get total balance of stable coins assinged to agents
1036          '''
1037          return self.contract.caller({'from' : agent.address, 'gas': 100000}).getTotalBalance()
1038  
1039      def get_token_stock(self, agent):
1040          '''
1041              Get total balance erc20 stables coins deposited into credit provider
1042          '''
1043          return self.contract.caller({'from' : agent.address, 'gas': 100000}).totalTokenStock()
1044  
1045      def get_total_debt(self, agent):
1046          return Balance(self.contract.caller({'from' : agent.address, 'gas': 8000000}).totalDebt(), EXCHG['decimals'])
1047  
1048      def get_short_collateral_exposure(self, agent):
1049          return Balance(self.contract.caller({'from' : agent.address, 'gas': 8000000}).calcRawCollateralShortage(agent.address), EXCHG['decimals'])
1050  
1051  class LinearLiquidityPool(TokenProxy):
1052      def __init__(self, contract, usdt_token, options_exchange, **kwargs):
1053          self.usdt_token = usdt_token
1054          self.options_exchange = options_exchange
1055          super().__init__(contract)
1056  
1057      def deposit_pool(self, agent, amount):
1058          '''
1059              pool.depositTokens(
1060                  address to, address token, uint value
1061              );
1062          '''
1063          self.usdt_token.ensure_approved(agent, self.contract.address)
1064          tx = transaction_helper(
1065              agent,
1066              self.contract.functions.depositTokens(
1067                  agent.address,
1068                  self.usdt_token.address,
1069                  amount.to_wei()
1070              ),
1071              500000
1072          )
1073          return tx
1074  
1075      def redeem_pool(self, agent):
1076          '''
1077              pool.redeem(address)
1078          '''
1079          tx = transaction_helper(
1080              agent,
1081              self.contract.functions.redeem(
1082                  agent.address
1083              ),
1084              8000000
1085          )
1086          return tx
1087  
1088      def query_buy(self, agent, symbol):
1089          print(symbol)
1090          price_volume = self.contract.caller({'from' : agent.address, 'gas': 80000000}).queryBuy(symbol)
1091          return price_volume
1092  
1093      def buy(self, agent, symbol, price, volume):
1094          '''
1095              stablecoin.approve(address(pool), price * volume / volumeBase);
1096              pool.buy(symbol, price, volume, address(stablecoin));
1097          '''
1098          self.usdt_token.ensure_approved(agent, self.contract.address)
1099          tx = transaction_helper(
1100              agent,
1101              self.contract.functions.buy(
1102                  symbol,
1103                  price,
1104                  volume.to_wei(),
1105                  self.usdt_token.contract.address
1106              ),
1107              8000000
1108          )
1109          return tx
1110  
1111      def query_sell(self, agent, symbol):
1112          price_volume = self.contract.caller({'from' : agent.address, 'gas': 80000000}).querySell(symbol)
1113          return price_volume
1114  
1115      def sell(self, agent, symbol, price, volume, option_token):
1116          '''
1117              option_token.approve(address(pool), price * volume / volumeBase)`;
1118              pool.sell(symbol, price, volume, 0, 0)`;
1119          '''
1120          '''
1121          txc = transaction_helper(
1122              agent,
1123              self.options_exchange.contract.functions.setCollateral(
1124                  self.contract.address
1125              ),
1126              8000000
1127          )
1128          w3.eth.waitForTransactionReceipt(txc, poll_latency=tx_pool_latency)
1129          '''
1130  
1131          option_token.ensure_approved(agent, self.contract.address)
1132          tx = transaction_helper(
1133              agent,
1134              self.contract.functions.sell(
1135                  symbol,
1136                  price,
1137                  volume.to_wei()
1138              ),
1139              8000000
1140          )
1141          return tx
1142  
1143      '''
1144          EXTRACT TIMESTAMP FROM SYMBOL AND FILTER ON IF BEFORE OR AFTER CURRENT BLOCK TIMESTAMP
1145      '''
1146      def list_symbols(self, agent):
1147          symbols = self.contract.caller({'from' : agent.address, 'gas': 8000000}).listSymbols().split('\n')
1148          return [x for x in list(filter(None,symbols)) if x != '']
1149  
1150      '''
1151          EXTRACT TIMESTAMP FROM SYMBOL AND FILTER ON IF BEFORE OR AFTER CURRENT BLOCK TIMESTAMP
1152      '''
1153      def list_expired_symbols(self, agent):
1154          symbols = self.contract.caller({'from' : agent.address, 'gas': 8000000}).listExpiredSymbols().split('\n')
1155          return [x for x in list(filter(None,symbols)) if x != '']
1156  
1157      def get_option_tokens(self, agent):
1158          symbols = self.list_symbols(agent)
1159          option_tokens = []
1160          for sym in symbols:
1161              option_token_address = self.options_exchange.resolve_token(agent, sym)
1162              if option_token_address:
1163                  option_token = TokenProxy(w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address))
1164                  option_tokens.append(option_token)
1165  
1166          return option_tokens
1167  
1168      def get_option_tokens_expired(self, agent):
1169          symbols = self.list_expired_symbols(agent)
1170          option_tokens = []
1171          for sym in symbols:
1172              option_token_address = self.options_exchange.resolve_token(agent, sym)
1173              if option_token_address:
1174                  option_token = TokenProxy(w3.eth.contract(abi=OptionTokenContract['abi'], address=option_token_address))
1175                  option_tokens.append(option_token)
1176  
1177          return option_tokens
1178  
1179      def add_symbol(self, agent, udlfeed_address, strike, maturity, option_type, current_timestamp, x, y, buyStock, sellStock):
1180          '''
1181              pool.addSymbol(
1182                  address(feed),
1183                  strike,
1184                  maturity,
1185                  CALL,
1186                  time.getNow(),
1187                  time.getNow() + 1 days,
1188                  x,
1189                  y,
1190                  100 * volumeBase, // buy stock
1191                  200 * volumeBase  // sell stock
1192              );
1193          '''
1194          tx = transaction_helper(
1195              agent,
1196              self.contract.functions.addSymbol(
1197                  udlfeed_address,
1198                  strike * (10**EXCHG['decimals']),
1199                  maturity,
1200                  0 if option_type == 'CALL' else 1,
1201                  current_timestamp,
1202                  current_timestamp + (60 * 60 * 24 * 2),
1203                  x,
1204                  y,
1205                  buyStock * 10**EXCHG['decimals'],
1206                  sellStock * 10**EXCHG['decimals']
1207              ),
1208              8000000
1209          )
1210          return tx
1211  
1212      def update_symbol(self, agent, udlfeed_address, strike, maturity, option_type, current_timestamp, x, y, buyStock, sellStock):
1213          '''
1214              pool.addSymbol(
1215                  address(feed),
1216                  strike,
1217                  maturity,
1218                  CALL,
1219                  time.getNow(),
1220                  time.getNow() + 1 days,
1221                  x,
1222                  y,
1223                  100 * volumeBase, // buy stock
1224                  200 * volumeBase  // sell stock
1225              );
1226          '''
1227          tx = transaction_helper(
1228              agent,
1229              self.contract.functions.addSymbol(
1230                  udlfeed_address,
1231                  strike * (10**EXCHG['decimals']),
1232                  maturity,
1233                  0 if option_type == 'CALL' else 1,
1234                  current_timestamp,
1235                  current_timestamp + (60 * 60 * 24 * 2),
1236                  x,
1237                  y,
1238                  buyStock * (10**EXCHG['decimals']),
1239                  sellStock * (10**EXCHG['decimals'])
1240              ),
1241              8000000
1242          )
1243          return tx
1244  
1245      def pool_free_balance(self, agent):
1246          pool_free_balance = self.contract.caller({'from' : agent.address, 'gas': 100000}).calcFreeBalance()
1247          return Balance(pool_free_balance, EXCHG['decimals'])
1248  
1249  class Model:
1250      """
1251      Full model of the economy.
1252      """
1253      
1254      def __init__(self, options_exchange, credit_provider, linear_liquidity_pool, btcusd_chainlink_feed, btcusd_agg, btcusd_data, xsd, usdt, agents, **kwargs):
1255          """
1256          Takes in experiment parameters and forwards them on to all components.
1257          """
1258  
1259          self.agents = []
1260          self.options_exchange = OptionsExchange(options_exchange, usdt, btcusd_chainlink_feed, **kwargs)
1261          self.credit_provider = CreditProvider(credit_provider, **kwargs)
1262          self.linear_liquidity_pool = LinearLiquidityPool(linear_liquidity_pool, usdt, self.options_exchange, **kwargs)
1263          self.btcusd_chainlink_feed = btcusd_chainlink_feed
1264          self.btcusd_agg = btcusd_agg
1265          self.btcusd_data = btcusd_data
1266          self.btcusd_data_init_bins = 30
1267          self.current_round_id = 30
1268          self.daily_vol_period = 30
1269          self.prev_timestamp = 0
1270          self.daily_period = 60 * 60 * 24
1271          self.weekly_period = self.daily_period * 7
1272          self.days_per_year = 365
1273          self.months_per_year = 12
1274          self.option_tokens = {}
1275          self.option_tokens_expired = {}
1276          self.option_tokens_expired_to_burn = {}
1277          self.usdt_token = usdt
1278          self.symbol_created = {}
1279  
1280          is_mint = is_try_model_mine
1281          if w3.eth.get_block('latest')["number"] == block_offset:
1282              # THIS ONLY NEEDS TO BE RUN ON NEW CONTRACTS
1283              # TODO: tolerate redeployment or time-based generation
1284              is_mint = True
1285          
1286          total_tx_submitted = len(agents) 
1287          for i in range(len(agents)):
1288              
1289              address = agents[i]
1290              agent = Agent(self.linear_liquidity_pool, self.options_exchange, self.credit_provider, xsd, usdt, starting_axax=0, starting_usdt=0, wallet_address=address, is_mint=is_mint, **kwargs)
1291               
1292              self.agents.append(agent)
1293  
1294  
1295          is_print_agent_state = False
1296  
1297          if is_print_agent_state:
1298              # Update caches to current chain state
1299              self.usdt_token.update(is_init_agents=self.agents)
1300              self.linear_liquidity_pool.update(is_init_agents=self.agents)
1301  
1302              for x in self.linear_liquidity_pool.get_option_tokens(self.agents[0]):
1303                  if x.address not in self.option_tokens:
1304                      self.option_tokens[x.address] = x
1305  
1306              self.options_exchange.option_tokens = self.option_tokens
1307  
1308  
1309              for i in range(len(agents)):
1310                  logger.info(self.agents[i])
1311  
1312              sys.exit()
1313  
1314  
1315          '''
1316              INIT T-MINUS DATA FOR FEED
1317          '''
1318  
1319          if self.prev_timestamp == 0:
1320              current_timestamp = w3.eth.get_block('latest')['timestamp']
1321              seleted_advancer = self.agents[0]
1322              transaction_helper(
1323                  seleted_advancer,
1324                  self.btcusd_agg.functions.setRoundIds(
1325                      range(30)
1326                  ),
1327                  500000
1328              )
1329  
1330              transaction_helper(
1331                  seleted_advancer,
1332                  self.btcusd_agg.functions.setAnswers(
1333                      self.btcusd_data[:self.btcusd_data_init_bins]
1334                  ),
1335                  500000
1336              )
1337  
1338              timestamps = [(x* self.daily_period * -1) + current_timestamp for x in range(self.btcusd_data_init_bins, 0, -1)]
1339              transaction_helper(
1340                  seleted_advancer,
1341                  self.btcusd_agg.functions.setUpdatedAts(
1342                      timestamps
1343                  ),
1344                  500000
1345              )
1346  
1347              print(timestamps)
1348              print(self.btcusd_data[:self.btcusd_data_init_bins])
1349  
1350              tx = transaction_helper(
1351                  seleted_advancer,
1352                  self.btcusd_chainlink_feed.functions.initialize(
1353                      timestamps,
1354                      self.btcusd_data[:self.btcusd_data_init_bins]
1355                  ),
1356                  8000000
1357              )
1358              receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600)
1359              print(receipt)
1360  
1361          
1362      def log(self, stream, seleted_advancer, header=False):
1363          """
1364          Log model statistics a TSV line.
1365          If header is True, include a header.
1366          """
1367          
1368          if header:
1369              stream.write("#block\twritten\tholding\texposure\ttotal CB\ttotal SB\ttotal debt\n")#\tfaith\n")
1370          
1371          print(
1372              w3.eth.get_block('latest')["number"],
1373              self.options_exchange.get_total_written(seleted_advancer),
1374              self.options_exchange.get_total_holding(seleted_advancer),
1375              self.options_exchange.get_total_short_collateral_exposure(seleted_advancer),
1376              self.credit_provider.get_total_balance(seleted_advancer),
1377              self.credit_provider.get_token_stock(seleted_advancer),
1378              self.credit_provider.get_total_debt(seleted_advancer)
1379          )
1380          
1381          stream.write('{}\t{}\t{}\t{:.2f}\t{:.2f}\t{:.2f}\t{:.2f}\n'.format(
1382                  w3.eth.get_block('latest')["number"],
1383                  self.options_exchange.get_total_written(seleted_advancer),
1384                  self.options_exchange.get_total_holding(seleted_advancer),
1385                  self.options_exchange.get_total_short_collateral_exposure(seleted_advancer),
1386                  self.credit_provider.get_total_balance(seleted_advancer),
1387                  self.credit_provider.get_token_stock(seleted_advancer),
1388                  self.credit_provider.get_total_debt(seleted_advancer)
1389              )
1390          )
1391         
1392      def get_overall_faith(self):
1393          """
1394          Probably should be related to credit token?
1395          """
1396          pass
1397  
1398      def is_positve_option_token_expired_balance(self, agent):
1399          tokens = []
1400          for k,v in self.option_tokens_expired.items():
1401              if v[agent] > 0:
1402                  tokens.append(v)
1403          return False
1404      
1405      def is_positive_option_token_balance(self, agent):
1406          tokens = []
1407          for k,v in self.option_tokens.items():
1408              if v[agent] > 0:
1409                  tokens.append(v)
1410  
1411          for k,v in self.option_tokens_expired.items():
1412              if v[agent] > 0:
1413                  tokens.append(v)
1414          return False
1415         
1416      def step(self):
1417          """
1418          Step the model Let all the agents act.
1419          
1420          Returns True if anyone could act.
1421          """
1422          # Update caches to current chain state for all the tokens
1423          self.usdt_token.update()
1424          self.linear_liquidity_pool.update()
1425  
1426          current_timestamp = w3.eth.get_block('latest')['timestamp']
1427          diff_timestamp = current_timestamp - self.prev_timestamp
1428  
1429          '''
1430              TODO:
1431                  randomly have an agent do maintence tasks the epoch, in order to simulate people using governance tokens to do these task, for now, use initializing agent
1432          '''
1433          random_advancer = self.agents[int(random.random() * (len(self.agents) - 1))]
1434          seleted_advancer = self.agents[0]
1435  
1436          available_symbols = self.linear_liquidity_pool.list_symbols(seleted_advancer)
1437  
1438  
1439          '''
1440              TODO: need to explore 2:1 bs, 1:1 bs and 1:2 bs
1441          '''
1442  
1443          buyStock = 100000
1444          sellStock = 100000
1445  
1446          
1447          for x in self.linear_liquidity_pool.get_option_tokens(random_advancer):
1448              if x.address not in self.option_tokens:
1449                  self.option_tokens[x.address] = x
1450  
1451          for x in self.linear_liquidity_pool.get_option_tokens_expired(random_advancer):
1452              if x.totalSupply >= 1:
1453                  self.option_tokens_expired[x.address] = x
1454  
1455              else:
1456                  self.option_tokens_expired_to_burn[x.address] = x
1457  
1458              # need to remove expired tokens from reset for long running sims
1459              if x.address in self.option_tokens:
1460                  del self.option_tokens[x.address]
1461  
1462          self.options_exchange.option_tokens = self.option_tokens
1463  
1464          '''
1465              UPDATE FEEDS WHEN LASTEST DAY PASSESS
1466              UPDATE SYMBOL PARAMS
1467          '''
1468  
1469          #if (datetime.datetime.fromtimestamp(current_timestamp, tz=datetime.timezone.utc).date() > datetime.datetime.fromtimestamp(self.prev_timestamp, tz=datetime.timezone.utc).date()):
1470          if (datetime.datetime.fromtimestamp(current_timestamp).date() > datetime.datetime.fromtimestamp(self.prev_timestamp).date()):
1471              
1472              if self.prev_timestamp > 0:
1473                  # increment round id
1474                  self.current_round_id += 1
1475  
1476              tx = transaction_helper(
1477                  seleted_advancer,
1478                  self.btcusd_agg.functions.appendRoundId(
1479                      self.current_round_id
1480                  ),
1481                  500000
1482              )
1483              receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600)
1484              print("appendRoundId:", receipt)
1485  
1486              tx = transaction_helper(
1487                  seleted_advancer,
1488                  self.btcusd_agg.functions.appendAnswer(
1489                      self.btcusd_data[self.current_round_id]
1490                  ),
1491                  500000
1492              )
1493              receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600)
1494              print("appendAnswer:", receipt)
1495  
1496              tx = transaction_helper(
1497                  seleted_advancer,
1498                  self.btcusd_agg.functions.appendUpdatedAt(
1499                      current_timestamp
1500                  ),
1501                  500000
1502              )
1503              receipt = w3.eth.waitForTransactionReceipt(tx, poll_latency=tx_pool_latency, timeout=600)
1504              print("appendUpdatedAt:", receipt)
1505  
1506              self.options_exchange.prefetch_daily(seleted_advancer, self.current_round_id, self.daily_vol_period * self.daily_period)
1507  
1508  
1509              '''
1510                  Prepare JSON data
1511  
1512                  {
1513                      "BTC/USD": {
1514                          "vol": 0.04,
1515                          "data": [
1516                              {"strike": 1234, "option_type": "PUT", vol}
1517                          ],
1518                      }
1519                  }
1520              '''
1521              maturity = None
1522              with open('mcmc_symbol_params.json', 'w+') as f:
1523                  mcmc_data = {}
1524                  for sym in available_symbols:
1525                      print('update symbol param file:', sym)
1526                      sym_parts = sym.split('-')
1527  
1528                      strike = int(float(sym_parts[2]) / 10**EXCHG['decimals'])
1529                      maturity = int(sym_parts[3])
1530                      
1531                      option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL'
1532                      current_price = self.btcusd_data[self.current_round_id] / (10.**BTCUSDAgg['decimals'])
1533  
1534                      if sym_parts[0] in mcmc_data:
1535                          # just append new strike data
1536                          mcmc_data[sym_parts[0]]["data"].append({
1537                              "strike": strike, 
1538                              "option_type": option_type,
1539                              "symbol": sym
1540                          })
1541                      else:
1542                          # need to calc feed vol
1543                          # NEED TO MAKE SURE THAT THE DECIMALS ARE CORRECT WHEN NORMING VOL
1544                          try:
1545                              vol = self.btcusd_chainlink_feed.caller({'from' : random_advancer.address, 'gas': 8000000}).getDailyVolatility(
1546                                  self.daily_vol_period * self.daily_period
1547                              )
1548                          except Exception as inst:
1549                              print(inst, "bad vol calc")
1550                              continue
1551  
1552                          multiplier = 3.0
1553                          tvol = (vol / (10.**EXCHG['decimals']))
1554                          normed_vol = math.log((current_price + (tvol * multiplier)) / (current_price - (tvol * multiplier)))
1555  
1556                          mcmc_data[sym_parts[0]] = {}
1557                          mcmc_data[sym_parts[0]]["curr_price"] = current_price
1558                          mcmc_data[sym_parts[0]]["vol"] = normed_vol
1559                          mcmc_data[sym_parts[0]]["data"] = [{
1560                              "strike": strike, 
1561                              "option_type": option_type,
1562                              "symbol": sym
1563                          }]
1564  
1565                  f.write(json.dumps(mcmc_data, indent=4))
1566  
1567  
1568              
1569  
1570              if maturity != None:
1571                  '''
1572                      EXECUTE MODEL HERE FOR ALL DATA
1573                      EXAMPLE: ./op_model "2000" "0.2" "3.0"
1574                  '''
1575                  days_until_expiry = (maturity - current_timestamp) / self.daily_period
1576                  months_to_exp = days_until_expiry / (self.days_per_year / 12.0)
1577                  num_samples = 2000
1578  
1579                  cmd = './op_model "%s" "%s" "%s"' % (
1580                      num_samples,
1581                      0.05,
1582                      months_to_exp,
1583                  )
1584                  model_ret = str(execute_cmd(cmd))
1585  
1586  
1587                  '''
1588                      LOAD IN FILE AND MAP DATA TO PAIR
1589                  '''
1590                  with open('mcmc_symbol_computation.json', 'r+') as f:
1591                      mcmc_symbol_computation = json.loads(f.read())
1592                      if mcmc_symbol_computation:
1593                          for sym in available_symbols:
1594                              print('update symbol:', sym)
1595                              sym_parts = sym.split('-')
1596  
1597                              if (sym in mcmc_symbol_computation) and mcmc_symbol_computation[sym]:
1598                                  x0s = mcmc_symbol_computation[sym]['x']
1599                                  if len(x0s) == 0:
1600                                      continue
1601  
1602                                  try:
1603                                      x = [Balance.from_tokens(round(x0,4), EXCHG['decimals']).to_wei() for x0 in x0s]
1604                                      y = mcmc_symbol_computation[sym]['y0'] + mcmc_symbol_computation[sym]['y1']
1605                                      y  = [Balance.from_tokens(round(y0,4), EXCHG['decimals']).to_wei() for y0 in y]
1606                                      print(x)
1607                                      print(y)
1608                                  except Exception as inst:
1609                                      print(inst, "no timestamp data to update")
1610                                      x = [0, 0]
1611                                      y = [0, 0, 0, 0]
1612  
1613                                  strike = int(float(sym_parts[2]) / 10**EXCHG['decimals'])
1614                                  option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL'
1615                                  current_timestamp = w3.eth.get_block('latest')['timestamp']
1616                                  sym_upd8_tx = self.linear_liquidity_pool.update_symbol(seleted_advancer, self.btcusd_chainlink_feed.address, strike, int(sym_parts[3]), option_type, current_timestamp, x, y, buyStock, sellStock)
1617                                  receipt = w3.eth.waitForTransactionReceipt(sym_upd8_tx, poll_latency=tx_pool_latency, timeout=600)
1618                                  print('update hash:', receipt)
1619  
1620              
1621              '''
1622                  TODO: NEED TO DUMP TO FILE AND LOAD FROM FILE IN ORDER TO BE ABLE TO START AND STOP MORE ROBUSTLY SANS NUKING CONTRACTS
1623              '''
1624              self.prev_timestamp = current_timestamp
1625  
1626              print("current prev_timestamp daily:", self.prev_timestamp)
1627  
1628  
1629          logger.info("Clock: {}".format(current_timestamp))
1630          logger.info("current_round_id: {}".format(self.current_round_id))
1631  
1632          shuffled_agents = list(range(len(self.agents)))
1633          random.shuffle(shuffled_agents)
1634  
1635          # return list of addres for agents who are short collateral
1636          any_short_collateral = []
1637          while True:
1638              try:
1639                  any_short_collateral = [a for a in self.agents if self.options_exchange.calc_collateral_surplus(random_advancer, a) <= 0]
1640                  break
1641              except Exception as inst:
1642                  print(inst, 'trying to pretetch sample')
1643                  self.options_exchange.prefetch_sample(random_advancer)
1644                  break
1645  
1646          
1647          tx_hashes = []
1648          total_tx_submitted = 0
1649  
1650          unique_available_symbols = list(set([asym.split('-')[1] for asym in available_symbols]))
1651          tks_list_symbols = [tv.symbol for tv in list(self.option_tokens.values())]
1652  
1653          pool_free_balance = self.linear_liquidity_pool.pool_free_balance(random_advancer)
1654          logger.info("pool_free_balance: {}".format(pool_free_balance))
1655  
1656          any_calls = any([ts for ts in available_symbols if '-EC-' in ts])
1657          any_puts = any([ts for ts in available_symbols if '-EP-' in ts])
1658  
1659          print("available_symbols:", available_symbols, len(available_symbols), len(tks_list_symbols))
1660          print((not any_calls or not any_puts))
1661  
1662          self.has_tried_liquidating = False
1663  
1664          max_symbols_per_type = 10
1665  
1666          for agent_num in shuffled_agents:            
1667              # TODO: real strategy
1668              a = self.agents[agent_num]
1669              options = []
1670  
1671              open_option_tokens = self.is_positive_option_token_balance(a)
1672              open_option_expired_tokens = self.is_positve_option_token_expired_balance(a)
1673  
1674              exchange_bal  = self.options_exchange.balance(a)
1675              pool_free_balance = self.linear_liquidity_pool.pool_free_balance(a)
1676              exchange_free_bal = Balance.from_tokens(0, EXCHG['decimals'])
1677              try:
1678                  exchange_free_bal = self.options_exchange.calc_collateral_surplus(a, a)
1679              except Exception as inst:
1680                  pass
1681  
1682  
1683              #'''
1684              #TODO: NEED A BETTER WAY TO FIGURE THIS OUT FOR SYMBOLS THAT FAIL TO UPDATE/HAVE ANY POS TXs
1685              available_symbols = self.linear_liquidity_pool.list_symbols(seleted_advancer)
1686              any_calls = [ts for ts in available_symbols if '-EC-' in ts]
1687              any_puts = [ts for ts in available_symbols if '-EP-' in ts]
1688  
1689              if len(available_symbols) != len(self.option_tokens):
1690                  #TRY AND UPDATE OPTION TOKENS AVAILABLE
1691                  for x in self.linear_liquidity_pool.get_option_tokens(seleted_advancer):
1692                      if x.address not in self.option_tokens:
1693                          self.option_tokens[x.address] = x
1694  
1695              tks_list_symbols = [tv.symbol for tv in list(self.option_tokens.values())]
1696              self.options_exchange.option_tokens = self.option_tokens
1697              #'''
1698  
1699              if exchange_bal > 0 and len(available_symbols) > 0 and (pool_free_balance > exchange_bal):
1700                  options.append('write')
1701  
1702              if (exchange_bal > 0 or pool_free_balance > 0) and ((len(any_calls) < max_symbols_per_type or len(any_puts) < max_symbols_per_type) or (len(available_symbols) < max_symbols_per_type*2)):
1703                  options.append('add_symbol')
1704  
1705              if len(available_symbols) != len(tks_list_symbols):
1706                  options.append('create_symbol')
1707  
1708  
1709              '''
1710              if (exchange_bal > 0) and len(self.option_tokens) > 0 and (a.total_written <= a.total_holding):
1711                  options.append('buy')
1712              '''
1713  
1714              if (a.total_holding > 1 or a.total_written > 1) and (pool_free_balance > 0):
1715                  options.append('sell')
1716  
1717              if (a.usdt > 0 and a.total_written < 1 and len(available_symbols) != len(self.option_tokens)) or (len(available_symbols) < max_symbols_per_type*2):
1718                  options.append('deposit_exchange')
1719  
1720              #len(available_symbols) != len(self.option_tokens) is to limit deposits to pool
1721              if (a.usdt > 0 and a.total_written < 1 and a.total_holding < 1 and len(available_symbols) != len(self.option_tokens)) or (len(available_symbols) < max_symbols_per_type*2):
1722                  options.append('deposit_pool')
1723  
1724              if exchange_free_bal > 0:
1725                  options.append('withdraw')
1726  
1727              '''
1728              TODO:
1729                  can only redeem after pool expires
1730              if a.lp > 0:
1731                  options.append('redeem_pool')
1732              '''
1733  
1734              # option position must be short collateral
1735              '''
1736              TODO:
1737                  Test later, since only book positions are pretty much have the pool as the owner for options issued
1738              '''
1739              if len(any_short_collateral) > 0 and not self.has_tried_liquidating:
1740                  options.append('liquidate')
1741  
1742              # liquidate expired positons
1743              if (len(any_short_collateral) > 0) or open_option_expired_tokens or (len(self.option_tokens_expired) > 0):
1744                  options.append('liquidate_self')
1745                  options.append('redeem_token')
1746  
1747              #'''
1748              if len(self.option_tokens_expired_to_burn) > 0 or len(self.option_tokens) > 0:
1749                  options.append('burn_token')
1750              #'''
1751  
1752  
1753              start_tx_count = a.next_tx_count
1754              commitment = random.random() * 0.01
1755  
1756              if len(options) > 0:
1757                  # We can act
1758  
1759                  '''
1760                      LATER:
1761                          advance: to do maintainence functions, payout from dynamic collateral and/or gov token
1762                      
1763                      TODO:
1764                          burn_pool?
1765  
1766                      TOTEST:
1767                          burn_pool?
1768  
1769                      TOTESTLATER:
1770                          burn_token (may not be needed, handled by liquidation of option token), redeem_pool (can only reedeem after expiration)
1771                      WORKS:
1772                          deposit_exchange, deposit_pool, add_symbol, update_symbol, write, create_symbol, buy, liquidate_self, liquidate, withdraw, sell, redeem_token
1773                          
1774                  '''
1775          
1776                  strategy = a.get_strategy(w3.eth.get_block('latest')["number"])
1777                  
1778                  weights = [strategy[o] for o in options]
1779                  
1780                  action = random.choices(options, weights=weights)[0]
1781                  
1782                  # What fraction of the total possible amount of doing this
1783                  # action will the agent do?
1784                  
1785                  
1786                  if action == "deposit_exchange":
1787                      amount = portion_dedusted(
1788                          a.usdt,
1789                          commitment * 0.1
1790                      )
1791                      try:
1792                          dpe_hash = self.options_exchange.deposit_exchange(a, amount)
1793                          tx_hashes.append({'type': 'deposit_exchange', 'hash': dpe_hash})
1794                      except Exception as inst:
1795                          logger.info({"agent": a.address, "error": inst, "action": "deposit_exchange", "amount": amount})
1796                  elif action == "add_symbol":
1797                      option_types = ['PUT', 'CALL']
1798                      # NEED TO MAKE SURE THAT THE DECIMALS ARE CORRECT WHEN NORMING STRIKES
1799                      current_price = self.btcusd_data[self.current_round_id] / (10.**BTCUSDAgg['decimals'])
1800  
1801                      """
1802                      TODO:
1803                          choose random maturity length less than the maturity of the pool? 1 month for now
1804                      """
1805                      current_timestamp = w3.eth.get_block('latest')['timestamp']
1806                      num_months = 6.0 # 1.0
1807                      maturity = int(current_timestamp + (self.daily_period * (self.days_per_year / self.months_per_year * num_months)))
1808                      days_until_expiry = (maturity - current_timestamp) / self.daily_period
1809                      months_to_exp = days_until_expiry / (self.days_per_year / 12.0)
1810                      num_samples = 2000
1811                      option_type = option_types[0 if random.random() > 0.5 else 1]
1812  
1813                      is_otm_only = False
1814  
1815                      moneyness = max(0.0, random.random())
1816  
1817                      if not is_otm_only:
1818                          if random.random() > 0.5:
1819                              strike = round(current_price * (1 + moneyness))
1820                          else:
1821                              strike = round(current_price * (1 - moneyness))
1822  
1823                      else:
1824                          if option_type == 'CALL':
1825                              # if call, write OTM by random amout, to the upside
1826                              strike = round(current_price * (1 + moneyness))
1827                          else:
1828                              # if put, write OTM by random amount, to the downside
1829                              strike = round(current_price * (1 - moneyness))
1830  
1831                      with open('mcmc_symbol_params.json', 'w+') as f:
1832                          mcmc_data = {}
1833                          # need to calc feed vol
1834                          # NEED TO MAKE SURE THAT THE DECIMALS ARE CORRECT WHEN NORMING VOL
1835                          try:
1836                              vol = self.btcusd_chainlink_feed.caller({'from' : random_advancer.address, 'gas': 8000000}).getDailyVolatility(
1837                                  self.daily_vol_period * self.daily_period
1838                              )
1839                          except Exception as inst:
1840                              print(inst, "bad vol calc")
1841                              continue
1842  
1843                          multiplier = 3.0
1844                          tvol = (vol / (10.**EXCHG['decimals']))
1845                          normed_vol = math.log((current_price + (tvol * multiplier)) / (current_price - (tvol * multiplier)))
1846  
1847                          mcmc_data["pending"] = {}
1848                          mcmc_data["pending"]["curr_price"] = self.btcusd_data[self.current_round_id] / (10.**BTCUSDAgg['decimals'])
1849                          mcmc_data["pending"]["vol"] = normed_vol
1850                          mcmc_data["pending"]["data"] = [{
1851                              "strike": strike, 
1852                              "option_type": option_type,
1853                              "symbol": "pending"
1854                          }]
1855  
1856                          f.write(json.dumps(mcmc_data, indent=4))
1857  
1858                      '''
1859                          EXECUTE MODEL HERE FOR ALL DATA
1860                      '''
1861  
1862                      '''
1863                          EXAMPLE: ./op_model "2000" "0.2" "3.0"
1864                      '''
1865                      cmd = './op_model "%s" "%s" "%s"' % (
1866                          num_samples,
1867                          0.05,
1868                          months_to_exp,
1869                      )
1870                      model_ret = str(execute_cmd(cmd))
1871  
1872  
1873                      '''
1874                          LOAD IN FILE AND MAP DATA TO PAIR
1875                      '''
1876                      with open('mcmc_symbol_computation.json', 'r+') as f:
1877                          mcmc_symbol_computation = json.loads(f.read())
1878                          if mcmc_symbol_computation:
1879                              if mcmc_symbol_computation["pending"]:
1880                                  x0s = mcmc_symbol_computation["pending"]['x']
1881                                  if len(x0s) == 0:
1882                                      continue
1883  
1884                                  try:
1885                                      x = [Balance.from_tokens(round(x0,4), EXCHG['decimals']).to_wei() for x0 in x0s]
1886                                      y = mcmc_symbol_computation["pending"]['y0'] + mcmc_symbol_computation["pending"]['y1']
1887                                      y  = [Balance.from_tokens(round(y0,4), EXCHG['decimals']).to_wei() for y0 in y]
1888                                      print(x)
1889                                      print(y)
1890                                  except Exception as inst:
1891                                      print ('failed to add_symbol')
1892                                      continue
1893  
1894                                  try:
1895                                      # must be the selected advancer or governane proposoal
1896                                      ads_hash = self.linear_liquidity_pool.add_symbol(seleted_advancer, self.btcusd_chainlink_feed.address, strike, maturity, option_type, current_timestamp, x, y, buyStock, sellStock)
1897                                      providerAvax.make_request("avax.issueBlock", {})
1898                                      receipt = w3.eth.waitForTransactionReceipt(ads_hash, poll_latency=tx_pool_latency, timeout=600)
1899                                      tx_hashes.append({'type': 'add_symbol', 'hash': ads_hash})
1900                                  except Exception as inst:
1901                                      logger.info({"agent": a.address, "error": inst, "action": "add_symbol", "strike": strike, "maturity": maturity, "x": x, "y": y, "normed_vol": normed_vol, "vol": vol})
1902                  elif action == "create_symbol":
1903                      for sym in available_symbols:
1904                          if sym not in tks_list_symbols:
1905  
1906                              try:
1907                                  cs_hash = self.options_exchange.create_symbol(a, sym, self.btcusd_chainlink_feed)
1908                                  providerAvax.make_request("avax.issueBlock", {})
1909                                  receipt = w3.eth.waitForTransactionReceipt(cs_hash, poll_latency=tx_pool_latency, timeout=600)
1910                                  tx_hashes.append({'type': 'create_symbol', 'hash': cs_hash})
1911                              except Exception as inst:
1912                                  logger.info({"agent": a.address, "error": inst, "action": "create_symbol", "sym": sym })
1913                                  continue
1914                  elif action == "deposit_pool":
1915                      amount = portion_dedusted(
1916                          a.usdt,
1917                          commitment * 0.1
1918                      )
1919                      try:
1920                          dpp_hash = self.linear_liquidity_pool.deposit_pool(a, amount)
1921                          tx_hashes.append({'type': 'deposit_pool', 'hash': dpp_hash})
1922                      except Exception as inst:
1923                          logger.info({"agent": a.address, "error": inst, "action": "deposit_pool", "amount": amount})
1924                  elif action == "withdraw":
1925                      amount = portion_dedusted(
1926                          exchange_free_bal,
1927                          commitment * 10.0
1928                      )
1929                      try:
1930                          logger.info("Before Withdraw; volume: {} exchange_free_bal: {}".format(amount, exchange_free_bal))
1931                          wtd_hash = self.options_exchange.withdraw(a, amount)
1932                          tx_hashes.append({'type': 'withdraw', 'hash': wtd_hash})
1933                      except Exception as inst:
1934                          logger.info({"agent": a.address, "error": inst, "action": "withdraw", "amount": amount})
1935                  elif action == "redeem_pool":
1936                      try:
1937                          logger.info("Before Redeem Pool Shares")
1938                          rdm_hash = self.linear_liquidity_pool.redeem_pool(a)
1939                          tx_hashes.append({'type': 'redeem_pool', 'hash': rdm_hash})
1940                      except Exception as inst:
1941                          logger.info({"agent": a.address, "error": inst, "action": "redeem_pool"})
1942                  elif action == "redeem_token":
1943                      for otk, otv in self.option_tokens_expired.items():
1944                          if otv[a] > 0:
1945                              try:
1946                                  logger.info("Before Redeem Option: {}, {}".format(otv.symbol, otv[a]))
1947                                  rdmt_hash = self.options_exchange.redeem_token(a, otk)
1948                                  tx_hashes.append({'type': 'redeem_token', 'hash': rdmt_hash})
1949                              except Exception as inst:
1950                                  logger.info({"agent": a.address, "error": inst, "action": "redeem_token", "option_token": otv.address})
1951                  elif action == "burn_token":
1952                      '''
1953                      for otk, otv in self.option_tokens_expired_to_burn.items():
1954                          if otv[a] > 0:
1955                              try:
1956                                  logger.info("Before Burn Expired Option: {}".format(otv.symbol))
1957                                  rdmt_hash = self.options_exchange.redeem_token(a, otk)
1958                                  tx_hashes.append({'type': 'burn_token', 'hash': rdmt_hash})
1959                              except Exception as inst:
1960                                  logger.info({"agent": a.address, "error": inst, "action": "burn_token", "option_token": otv.address})
1961                      '''
1962  
1963                      for otk, otv in self.option_tokens.items():
1964                          owv = Balance(otv.contract.caller({'from' : a.address, 'gas': 100000}).writtenVolume(a.address), EXCHG['decimals'])
1965                          if owv > 0 and otv[a] > 0:
1966                              # written > holding
1967                              token_amount = Balance(owv.to_wei() - otv[a].to_wei(), EXCHG['decimals'])
1968  
1969                              if token_amount > 0:
1970                                  try:
1971                                      logger.info("Before Burn Excess Options: {}, token_amount: {}".format(otv.symbol, token_amount))
1972                                      burn_hash = self.options_exchange.burn_token(a, otk, token_amount)
1973                                      tx_hashes.append({'type': 'burn_token', 'hash': burn_hash})
1974                                  except Exception as inst:
1975                                      logger.info({"agent": a.address, "error": inst, "action": "burn_token", "option_token": otv.address})
1976  
1977                  elif action == "write":
1978                      # select from available symbols
1979                      sym = available_symbols[int(random.random() * (len(available_symbols) - 1))]
1980                      sym_parts = sym.split('-')
1981  
1982                      '''
1983                          * sym is something like: `ETH/USD-EC-13e20-1611964800` which represents an ETH european call option with strike price US$ 1300 and maturity at timestamp `1611964800`.
1984                      '''
1985                      strike_price = int(float(sym_parts[2]))
1986                      maturity = int(sym_parts[3])
1987                      option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL'
1988                      amount = int(round(portion_dedusted(
1989                          buyStock,
1990                          commitment
1991                      )))
1992  
1993                      logger.info("Looking to Write; symbol: {}, strike_price: {}, amount: {}, exchange_bal: {}".format(sym, strike_price, amount, exchange_bal))
1994  
1995                      try:
1996                          cc  = self.options_exchange.calc_collateral(a, self.btcusd_chainlink_feed.address, option_type, amount, strike_price, maturity)
1997                          cc_s = self.options_exchange.calc_collateral_surplus(a, a)
1998                      except Exception as inst:
1999                          print(inst)
2000                          continue
2001  
2002                      if cc_s < 1:
2003                          continue
2004  
2005                      if(cc > cc_s):
2006                          amount /= (cc.to_wei() / cc_s.to_wei())
2007                          logger.info("Norm to Write; amount: {}".format(amount))
2008  
2009                      if amount < 1:
2010                          continue
2011  
2012                      try:
2013                          logger.info("Before Write; symbol: {}, strike_price: {}, amount: {}".format(sym, strike_price, amount))
2014                          w_hash = self.options_exchange.write(a, self.btcusd_chainlink_feed.address, option_type, amount, strike_price, maturity)
2015                          tx_hashes.append({'type': 'write', 'hash': w_hash})
2016                      except Exception as inst:
2017                          logger.info({"agent": a.address, "error": inst, "action": "write", "strike_price": strike_price, "maturity": maturity, "option_type": option_type, "amount": amount})
2018                  elif action == "buy":
2019                      tks_list = list(self.option_tokens.values())
2020                      option_token_to_buy = tks_list[int(random.random() * (len(tks_list) - 1))]
2021                      symbol = option_token_to_buy.contract.caller({'from' : a.address, 'gas': 8000000}).symbol()
2022                      option_token_balance_of_pool = Balance(
2023                          option_token_to_buy.contract.caller({'from' : a.address, 'gas': 8000000}).writtenVolume(self.linear_liquidity_pool.address),
2024                          EXCHG['decimals']
2025                      )
2026                      print(symbol, option_token_balance_of_pool)
2027                      try:
2028                          current_price_volume = self.linear_liquidity_pool.query_buy(a, symbol)
2029                      except Exception as inst:
2030                          print("\terror querying buy", inst)
2031                          continue
2032  
2033                      if option_token_balance_of_pool >= buyStock:
2034                          continue
2035                      
2036                      print(symbol, current_price_volume, option_token_balance_of_pool, exchange_bal, 'BUY')
2037                      volume = Balance(current_price_volume[1], EXCHG['decimals'])
2038  
2039                      price = current_price_volume[0]
2040  
2041                      if (volume * (price / 10.**EXCHG['decimals']) * 100) > exchange_bal:
2042                          '''
2043                          sym_parts = symbol.split('-')
2044                          strike_price = int(float(sym_parts[2]))
2045                          maturity = int(sym_parts[3])
2046                          option_type = 'PUT' if sym_parts[1] == 'EP' else 'CALL'
2047                          try:
2048                              cc = self.options_exchange.calc_collateral(a, self.btcusd_chainlink_feed.address, option_type, volume, strike_price, maturity)
2049                          except:
2050                              continue
2051                          '''          
2052                          mod_price = Balance(price, EXCHG['decimals'])
2053                          print("mod_price", mod_price, mod_price.to_decimals(EXCHG['decimals']), mod_price.to_decimals(EXCHG['decimals']).to_wei(), exchange_bal.to_wei(), volume)
2054                          volume = Balance.from_tokens(int((exchange_bal.to_wei() / mod_price.to_decimals(EXCHG['decimals']).to_wei()) / 10), EXCHG['decimals'])
2055  
2056  
2057                      if volume < 1:
2058                          volume = Balance.from_tokens(1, EXCHG['decimals'])
2059  
2060                      
2061                      try:
2062                          logger.info("Before Buy; symbol: {}, price: {}, volume: {}".format(symbol, price, volume))
2063                          buy_hash = self.linear_liquidity_pool.buy(a, symbol, price, volume)
2064                          providerAvax.make_request("avax.issueBlock", {})
2065                          tx_hashes.append({'type': 'buy', 'hash': buy_hash})
2066                      except Exception as inst:
2067                          logger.info({"agent": a.address, "error": inst, "action": "buy", "volume": volume, "price": price, "symbol": symbol})
2068                  elif action == "sell":
2069                      option_token_to_sell = None
2070                      symbol = None
2071                      volume_to_sell = Balance(0, EXCHG['decimals'])
2072                      current_price_volume = None
2073                      for k, ot in self.option_tokens.items():
2074                          volume_to_sell = Balance(ot.contract.caller({'from' : a.address, 'gas': 8000000}).balanceOf(a.address), EXCHG['decimals'])
2075                          if volume_to_sell > 1:
2076                              option_token_to_sell = ot
2077                              
2078                              if not option_token_to_sell:
2079                                  continue
2080                              
2081                              symbol = option_token_to_sell.contract.caller({'from' : a.address, 'gas': 8000000}).symbol()
2082                              try:
2083                                  current_price_volume = self.linear_liquidity_pool.query_sell(a, symbol)
2084                              except Exception as inst:
2085                                  print("\terror querying sell", inst)
2086                                  continue
2087  
2088                              if volume_to_sell >= sellStock:
2089                                  print(volume_to_sell, ">", sellStock, "CANT SELL")
2090                                  continue
2091  
2092                              break
2093  
2094                      if not current_price_volume:
2095                          continue
2096  
2097  
2098                      print(symbol, current_price_volume, volume_to_sell, a.total_written, a.total_holding, 'SELL')
2099                      
2100                      if a.total_written > 0 and ((a.total_written >= a.total_holding) or (a.total_written < a.total_holding)):
2101                          # for exchange writers
2102                          volume = volume_to_sell
2103                      elif a.total_holding > 0 and a.total_written == 0:
2104                          # for traders
2105                          volume = portion_dedusted(
2106                              volume_to_sell,
2107                              commitment
2108                          )
2109                      
2110                      if volume == 0:
2111                          volume = Balance.from_tokens(1, EXCHG['decimals'])
2112                      price = current_price_volume[0] - (int(current_price_volume[0] * 0.001))
2113                      
2114                      try:
2115                          logger.info("Before Sell; symbol: {}, price: {}, volume: {}".format(symbol, price, volume))
2116                          sell_hash = self.linear_liquidity_pool.sell(a, symbol, price, volume, option_token_to_sell)
2117                          tx_hashes.append({'type': 'sell', 'hash': sell_hash, "volume": volume, "price": price, "symbol": symbol})
2118                      except Exception as inst:
2119                          logger.info({"agent": a.address, "error": inst, "action": "sell", "volume": volume, "price": price, "symbol": symbol})
2120                  elif action == "liquidate":
2121                      for short_owner in any_short_collateral:
2122                          if short_owner.total_written > 1: 
2123                              for otk, otv in self.option_tokens.items():
2124                                  if otv.contract.caller({'from' : a.address, 'gas': 8000000}).writtenVolume(short_owner.address) > 0:
2125                                      try:
2126                                          lqd8_hash = self.options_exchange.liquidate(a, otk, short_owner.address)
2127                                          tx_hashes.append({'type': 'liquidate', 'hash': lqd8_hash})
2128                                      except Exception as inst:
2129                                          logger.info({"agent": a.address, "error": inst, "action": "liquidate", "short_owner": short_owner.address, "option_token": otk})
2130  
2131                      self.has_tried_liquidating = True
2132                  elif action == "liquidate_self":
2133                      for otk, otv in self.option_tokens_expired.items():
2134                          if otv[a] > 0:
2135                              try:
2136                                  lqd8_hash = self.options_exchange.liquidate(a, otk, self.linear_liquidity_pool.address)
2137                                  tx_hashes.append({'type': 'liquidate_self', 'hash': lqd8_hash})
2138                              except Exception as inst:
2139                                  logger.info({"agent": a.address, "error": inst, "action": "liquidate_self", "option_token": otk})
2140  
2141                      for otk, otv in self.option_tokens.items():
2142                          if otv[a] > 0:
2143                              try:
2144                                  lqd8_hash = self.options_exchange.liquidate(a, otk, a.address)
2145                                  tx_hashes.append({'type': 'liquidate_self', 'hash': lqd8_hash})
2146                              except Exception as inst:
2147                                  logger.info({"agent": a.address, "error": inst, "action": "liquidate_self", "short_owner": a.address, "option_token": otk})
2148  
2149                  else:
2150                      raise RuntimeError("Bad action: " + action)
2151                      
2152                  anyone_acted = True
2153              else:
2154                  # It's normal for agents other then the first to advance to not be able to act on block 0.
2155                  pass
2156  
2157              end_tx_count = a.next_tx_count
2158  
2159              total_tx_submitted += (end_tx_count - start_tx_count)
2160  
2161          providerAvax.make_request("avax.issueBlock", {})
2162  
2163          tx_hashes_good = 0
2164          tx_fails = []
2165          tx_good = []
2166          #'''
2167          for tmp_tx_hash in tx_hashes:
2168              receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency, timeout=600)
2169              tx_hashes_good += receipt["status"]
2170              if receipt["status"] == 0:
2171                  tx_fails.append(tmp_tx_hash)
2172              else:
2173                  tx_good.append(tmp_tx_hash)
2174  
2175          #'''
2176  
2177          logger.info("total tx: {}, successful tx: {}, tx fails: {}, tx passed: {}".format(
2178                  len(tx_hashes), tx_hashes_good, tx_fails, tx_good
2179              )
2180          )
2181  
2182          return True, random_advancer, tx_good
2183  
2184  def main():
2185      """
2186      Main function: run the simulation.
2187      """
2188      global avax_cchain_nonces
2189  
2190      '''
2191          curl -X POST --data '{ "jsonrpc":"2.0", "id" :1, "method" :"debug_increaseTime", "params" : ["0x45e8d9a7ca159a0f6957534cb25412b4daa4243906ef2b6e93125246b1e27d05"]}' -H 'content-type:application/json;' http://127.0.0.1:9545/ext/bc/C/rpc
2192      '''
2193      transaction = w3.eth.get_transaction("0x5d385ebc44b8e1e6c01b24ef2a13fa574649d0993cae859a63af64ecc11f5851")
2194  
2195      print(transaction.input)
2196      #print(provider.make_request("debug_traceTransaction", ["0x45e8d9a7ca159a0f6957534cb25412b4daa4243906ef2b6e93125246b1e27d05"]))
2197      #print(w3.eth.get_block('latest')['timestamp'])
2198      #
2199  
2200      
2201      logging.basicConfig(level=logging.INFO)
2202      logger.info('Total Agents: {}'.format(len(w3.eth.accounts[:max_accounts])))
2203  
2204      
2205      options_exchange = w3.eth.contract(abi=OptionsExchangeContract['abi'], address=EXCHG["addr"])
2206      proposal_wrapper = w3.eth.contract(abi=ProposalWrapperContract['abi'], address=PROPSWRPR["addr"])
2207      proposal_manager = w3.eth.contract(abi=ProposalManagerContract['abi'], address=PROPSMNGER["addr"])
2208      proposal_manager_helper = w3.eth.contract(abi=ProposalManagerHelperContract['abi'], address=PROPSMNGERHELPER["addr"])
2209      usdt = TokenProxy(w3.eth.contract(abi=USDTContract['abi'], address=USDT["addr"]))
2210      credit_provider = w3.eth.contract(abi=CreditProviderContract['abi'], address=CREDPRO["addr"])
2211      linear_liquidity_pool_factory = w3.eth.contract(abi=LinearLiquidityPoolFactoryContract['abi'], address=LLPF["addr"])
2212      protocol_settings = w3.eth.contract(abi=ProtocolSettingsContract['abi'], address=STG['addr'])
2213      btcusd_chainlink_feed = w3.eth.contract(abi=ChainlinkFeedContract['abi'], address=BTCUSDc['addr'])
2214      btcusd_agg = w3.eth.contract(abi=AggregatorV3MockContract['abi'], address=BTCUSDAgg["addr"])
2215  
2216      #print(btcusd_chainlink_feed.caller({'from' : w3.eth.accounts[0], 'gas': 8000000}).getLatestPrice())
2217      #sys.exit()
2218      
2219      mock_time = w3.eth.contract(abi=TimeProviderMockContract['abi'], address=TPRO["addr"])
2220  
2221  
2222      #''
2223      #print(credit_provider.caller({'from' : w3.eth.accounts[0], 'gas': 8000000}).balanceOf(linear_liquidity_pool.address))
2224      print("tx-input -> 0xa53b0041")
2225      print(proposal_wrapper.decode_function_input("0x5e4d3229"))
2226      #print(proposal_manager.decode_function_input(transaction.input))
2227  
2228      #print(
2229      #proposal_manager_helper.caller({'from' : w3.eth.accounts[0], 'gas': 8000000}).getExecutionBytes())
2230  
2231  
2232      #sys.exit()
2233  
2234      #''
2235      '''
2236          INIT FEEDS FOR BTCUSDAGG
2237      '''
2238      btcusd_historical_ohlc = []
2239  
2240      '''
2241      for acc in w3.eth.accounts[:max_accounts]:
2242          cs = options_exchange.caller({'from' : acc, 'gas': 8000000}).calcSurplus(acc)
2243          print(acc, cs)
2244          '''
2245      #print(linear_liquidity_pool.caller({'from' : w3.eth.accounts[:max_accounts][0], 'gas': 8000000}).listExpiredSymbols())
2246      #pretty(options_exchange.functions.resolveToken("BTC/USD-EP-147e18-1623989786").call(), indent=0)
2247      #print(linear_liquidity_pool.functions.totalSupply().call())
2248      #print(Balance(4.474093538197649, 18).to_wei())
2249  
2250      #sys.exit()
2251      '''
2252      with open('../../data/BTC-USD_vol_date_high_low_close.json', 'r+') as btcusd_file:
2253          btcusd_historical_ohlc = json.loads(btcusd_file.read())["chart"]
2254      
2255  
2256      daily_period = 60 * 60 * 24
2257      current_timestamp = int(w3.eth.get_block('latest')['timestamp'])
2258      print("current_timestamp", current_timestamp)
2259  
2260      btcusd_answers = []
2261      start_date = "2017-06-17"#"2017-12-17"
2262      btcusd_data_offset = 0
2263      btcusd_data_subtraction_set = 30 # look back period to present to seed data for vol calcs
2264      start_data_parsing = False # change this if you want to skip foward to specific time period
2265      for xidx, x in enumerate(btcusd_historical_ohlc):
2266  
2267  
2268          if start_data_parsing is False:
2269              if x['date'] == start_date:
2270                  start_data_parsing = True
2271                  btcusd_data_offset = xidx
2272  
2273  
2274          if x["open"] != 'null':
2275              btcusd_answers.append(int(float(x["open"]) * (10**BTCUSDAgg['decimals'])))
2276  
2277      if btcusd_data_offset <  btcusd_data_subtraction_set:
2278          btcusd_data_subtraction_set = btcusd_data_offset
2279  
2280      btcusd_answers = btcusd_answers[btcusd_data_offset-btcusd_data_subtraction_set:]
2281  
2282      print('btcusd_data_offset', btcusd_data_offset, start_date)
2283      '''
2284  
2285      avax_cchain_nonces = open(MMAP_FILE, "r+b")
2286  
2287      tx_hashes = []
2288      tx_hashes_good = 0
2289      tx_fails = []
2290  
2291      linear_liquidity_pool_address = None
2292      linear_liquidity_pool = None
2293      #''
2294    # opx = OptionsExchange(options_exchange, usdt, btcusd_chainlink_feed)
2295      opx = OptionsExchange(options_exchange, usdt, [])
2296      if not linear_liquidity_pool_address:
2297          # temp opx, llp, agent
2298          agent = Agent(None, opx, None, None, usdt, starting_axax=0, starting_usdt=0, wallet_address=w3.eth.accounts[0], is_mint=False)
2299          '''
2300          p_hash = transaction_helper(
2301              agent,
2302              opx.contract.functions.createPool(
2303                  "DEFAULT",
2304                  "TEST",
2305              ),
2306              8000000
2307          )
2308          tmp_tx_hash = {'type': 'createPool', 'hash': p_hash}
2309          print(tmp_tx_hash)
2310          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2311          tx_hashes.append(tmp_tx_hash)
2312          tx_hashes_good += receipt["status"]    
2313          if receipt["status"] == 0:
2314              print(receipt)
2315              tx_fails.append(tmp_tx_hash['type'])
2316          logs = options_exchange.events.CreatePool().processReceipt(receipt)
2317          print("linear pool address", logs[0].args.token)
2318          linear_liquidity_pool_address = logs[0].args.token
2319          linear_liquidity_pool = w3.eth.contract(abi=LinearLiquidityPoolContract["abi"], address=linear_liquidity_pool_address)
2320          '''
2321      else:
2322          linear_liquidity_pool = w3.eth.contract(abi=LinearLiquidityPoolContract["abi"], address=linear_liquidity_pool_address)
2323      '''
2324      _llp = LinearLiquidityPool(linear_liquidity_pool, usdt, opx)
2325      agent = Agent(_llp, opx, None, None, usdt, starting_axax=0, starting_usdt=0, wallet_address=w3.eth.accounts[0], is_mint=False)
2326      '''
2327      '''
2328          BELOW: USE FOR TESTNET FUNDING OF FAKE STABLECOIN
2329      '''
2330      
2331      sat_hash = transaction_helper(
2332          agent,
2333          protocol_settings.functions.setAllowedToken(
2334              usdt.address,
2335              1,
2336              10**(18 - usdt.decimals)
2337          ),
2338          500000
2339      )
2340      tmp_tx_hash = {'type': 'setAllowedToken', 'hash': sat_hash}
2341      tx_hashes.append(tmp_tx_hash)
2342      print(tmp_tx_hash)
2343      receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2344      tx_hashes_good += receipt["status"]
2345      if receipt["status"] == 0:
2346          print(receipt)
2347          tx_fails.append(tmp_tx_hash['type'])
2348  
2349      
2350  
2351      msp_hash= transaction_helper(
2352          agent,
2353          protocol_settings.functions.setMinShareForProposal(
2354              10,
2355              1000
2356          ),
2357          500000
2358      )
2359      tmp_tx_hash = {'type': 'setMinShareForProposal', 'hash': msp_hash}
2360      tx_hashes.append(tmp_tx_hash)
2361      print(tmp_tx_hash)
2362      receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2363      tx_hashes_good += receipt["status"]
2364      if receipt["status"] == 0:
2365          print(receipt)
2366          tx_fails.append(tmp_tx_hash['type'])
2367  
2368  
2369      
2370      sys.exit()
2371  
2372      '''
2373          ABOVE: FR TESTNET FUNDING OF FAKE STABLECOIN
2374      '''
2375  
2376  
2377      '''
2378          SETUP POOL:
2379              All options must have maturities under the pool maturity
2380      '''
2381  
2382  
2383      pool_spread = 5 * (10**7) #5% 
2384      pool_reserve_ratio = 0 * (10**7) # 20% default
2385      pool_maturity = (1000000000 * daily_period) + current_timestamp
2386  
2387      '''
2388          SETUP PROTOCOL SETTINGS FOR POOL
2389      '''
2390      skip = False
2391  
2392      if not skip:
2393          mt_hash = transaction_helper(
2394              agent,
2395              mock_time.functions.setFixedTime(
2396                  -1
2397              ),
2398              500000
2399          )
2400          tmp_tx_hash = {'type': 'setFixedTime', 'hash': mt_hash}
2401          tx_hashes.append(tmp_tx_hash)
2402          print(tmp_tx_hash)
2403          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2404          tx_hashes_good += receipt["status"]
2405          if receipt["status"] == 0:
2406              print(receipt)
2407              tx_fails.append(tmp_tx_hash['type'])
2408  
2409      if not skip:
2410          sp_hash = transaction_helper(
2411              agent,
2412              linear_liquidity_pool.functions.setParameters(
2413                  pool_spread,
2414                  pool_reserve_ratio,
2415                  pool_maturity
2416              ),
2417              500000
2418          )
2419          tmp_tx_hash = {'type': 'setParameters', 'hash': sp_hash}
2420          tx_hashes.append(tmp_tx_hash)
2421          print(tmp_tx_hash)
2422          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2423          tx_hashes_good += receipt["status"]
2424          if receipt["status"] == 0:
2425              print(receipt)
2426              tx_fails.append(tmp_tx_hash['type'])
2427      
2428      if not skip:
2429          so_hash = transaction_helper(
2430              agent,
2431              protocol_settings.functions.setOwner(
2432                  agent.address,
2433              ),
2434              500000
2435          )
2436          tmp_tx_hash = {'type': 'setOwner', 'hash': so_hash}
2437          tx_hashes.append(tmp_tx_hash)
2438          print(tmp_tx_hash)
2439          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2440          tx_hashes_good += receipt["status"]
2441          if receipt["status"] == 0:
2442              print(receipt)
2443              tx_fails.append(tmp_tx_hash['type'])
2444  
2445      
2446      if not skip:
2447          sat_hash = transaction_helper(
2448              agent,
2449              protocol_settings.functions.setAllowedToken(
2450                  usdt.address,
2451                  1,
2452                  10**(18 - usdt.decimals)
2453              ),
2454              500000
2455          )
2456          tmp_tx_hash = {'type': 'setAllowedToken', 'hash': sat_hash}
2457          tx_hashes.append(tmp_tx_hash)
2458          print(tmp_tx_hash)
2459          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2460          tx_hashes_good += receipt["status"]
2461          if receipt["status"] == 0:
2462              print(receipt)
2463              tx_fails.append(tmp_tx_hash['type'])
2464      
2465      if not skip:
2466          suf_hash = transaction_helper(
2467              agent,
2468              protocol_settings.functions.setUdlFeed(
2469                  btcusd_chainlink_feed.address,
2470                  1
2471              ),
2472              500000
2473          )
2474          tmp_tx_hash = {'type': 'setUdlFeed', 'hash': suf_hash}
2475          tx_hashes.append(tmp_tx_hash)
2476          print(tmp_tx_hash)
2477          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2478          tx_hashes_good += receipt["status"]
2479          if receipt["status"] == 0:
2480              print(receipt)
2481              tx_fails.append(tmp_tx_hash['type'])
2482  
2483      if not skip:
2484          svp_hash = transaction_helper(
2485              agent,
2486              protocol_settings.functions.setVolatilityPeriod(
2487                  30 * daily_period
2488              ),
2489              500000
2490          )
2491          tmp_tx_hash = {'type': 'setVolatilityPeriod', 'hash': svp_hash}
2492          tx_hashes.append(tmp_tx_hash)
2493          print(tmp_tx_hash)
2494          receipt = w3.eth.waitForTransactionReceipt(tmp_tx_hash['hash'], poll_latency=tx_pool_latency)
2495          tx_hashes_good += receipt["status"]
2496          if receipt["status"] == 0:
2497              print(receipt)
2498              tx_fails.append(tmp_tx_hash['type'])
2499  
2500  
2501      
2502      logger.info("total setup tx: {}, successful setup tx: {}, setup tx fails: {}".format(
2503              len(tx_hashes), tx_hashes_good, json.dumps(tx_fails)
2504          )
2505      )
2506  
2507      # Make a model of the options exchnage
2508      start_init = time.time()
2509      logger.info('INIT STARTED')
2510      model = Model(options_exchange, credit_provider, linear_liquidity_pool, btcusd_chainlink_feed, btcusd_agg, btcusd_answers, None, usdt, w3.eth.accounts[:max_accounts], min_faith=0.5E6, max_faith=1E6, use_faith=False)
2511      end_init = time.time()
2512      logger.info('INIT FINISHED {} (s)'.format(end_init - start_init))
2513  
2514      # Make a log file for system parameters, for analysis
2515      stream = open("log.tsv", "a+")
2516      
2517      for i in range(50000):
2518          # Every block
2519          # Try and tick the model
2520          start_iter = time.time()
2521  
2522          (anyone_acted, seleted_advancer, tx_passed) = model.step()
2523          if not anyone_acted:
2524              # Nobody could act
2525              logger.info("Nobody could act")
2526              break
2527          end_iter = time.time()
2528          logger.info('iter: %s, sys time %s' % (i, end_iter-start_iter))
2529          # Log system state
2530          model.log(stream, seleted_advancer, header=(i == 0))
2531  
2532          filtered_tx_passed = list(set([x['type'] for x in tx_passed]))
2533  
2534          if len(filtered_tx_passed) == 1:
2535              provider.make_request("debug_increaseTime", [3600 * 24])
2536          else:
2537              provider.make_request("debug_increaseTime", [3600 * 6])
2538          #'''
2539          #sys.exit()
2540          
2541  if __name__ == "__main__":
2542      main()