erc20-demurrage-token

ERC20 token with redistributed continual demurrage
Info | Log | Files | Refs | README

sim.py (11875B)


      1 # standard imports
      2 import logging
      3 
      4 # external imports
      5 from chainlib.chain import ChainSpec
      6 from chainlib.eth.unittest.ethtester import create_tester_signer
      7 from chainlib.eth.unittest.base import TestRPCConnection
      8 from chainlib.eth.tx import (
      9         receipt,
     10         Tx,
     11         )
     12 from chainlib.eth.nonce import RPCNonceOracle
     13 from chainlib.eth.gas import (
     14         OverrideGasOracle,
     15         Gas,
     16         )
     17 from chainlib.eth.address import to_checksum_address
     18 from chainlib.eth.block import (
     19         block_latest,
     20         block_by_number,
     21         block_by_hash,
     22         )
     23 from funga.eth.keystore.dict import DictKeystore
     24 from funga.eth.signer import EIP155Signer
     25 from hexathon import (
     26         strip_0x,
     27         add_0x,
     28         )
     29 
     30 # local imports
     31 from erc20_demurrage_token import DemurrageToken
     32 from erc20_demurrage_token.sim.error import TxLimitException
     33 
     34 logg = logging.getLogger(__name__)
     35 
     36 
     37 class DemurrageTokenSimulation:
     38 
     39     def __init__(self, chain_str, settings, redistribute=True, cap=0, actors=1):
     40         self.chain_spec = ChainSpec.from_chain_str(chain_str)
     41         self.accounts = []
     42         self.redistribute = redistribute
     43         self.keystore = DictKeystore()
     44         self.signer = EIP155Signer(self.keystore)
     45         self.eth_helper = create_tester_signer(self.keystore)
     46         self.eth_backend = self.eth_helper.backend
     47         self.gas_oracle = OverrideGasOracle(limit=100000, price=1)
     48         self.rpc = TestRPCConnection(None, self.eth_helper, self.signer)
     49         for a in self.keystore.list():
     50             self.accounts.append(add_0x(to_checksum_address(a)))
     51         settings.sink_address = self.accounts[0]
     52 
     53         self.actors = []
     54         for i in range(actors):
     55             idx = i % 10
     56             address = self.keystore.new()
     57             self.actors.append(address)
     58             self.accounts.append(address)
     59 
     60             nonce_oracle = RPCNonceOracle(self.accounts[idx], conn=self.rpc)
     61             c = Gas(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
     62             (tx_hash, o) = c.create(self.accounts[idx], address, 100000 * 1000000)
     63             self.rpc.do(o)
     64             o = receipt(tx_hash)
     65             r = self.rpc.do(o)
     66             if r['status'] != 1:
     67                 raise RuntimeError('failed gas transfer to account #{}: {} from {}'.format(i, address, self.accounts[idx]))
     68             logg.info('added actor account #{}: {} block {}'.format(i, address, r['block_number']))
     69 
     70         c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
     71         (tx_hash, o) = c.constructor(self.accounts[0], settings, redistribute=redistribute, cap=cap)
     72         self.rpc.do(o)
     73         o = receipt(tx_hash)
     74         r = self.rpc.do(o)
     75         if (r['status'] != 1):
     76             raise RuntimeError('contract deployment failed')
     77         self.address = r['contract_address']
     78         logg.info('deployed contract to {} block {}'.format(self.address, r['block_number']))
     79 
     80         o = block_latest()
     81         r = self.rpc.do(o)
     82         self.last_block = r
     83         self.start_block = self.last_block
     84 
     85         o = block_by_number(r)
     86         r = self.rpc.do(o)
     87         self.last_timestamp = r['timestamp']
     88         self.start_timestamp = self.last_timestamp
     89 
     90         nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc)
     91         o = c.decimals(self.address, sender_address=self.accounts[0])
     92         r = self.rpc.do(o)
     93         self.decimals = c.parse_decimals(r)
     94 
     95         self.period_seconds = settings.period_minutes * 60
     96 
     97         self.period = 1
     98         self.period_txs = []
     99         self.period_tx_limit = self.period_seconds - 1
    100         self.sink_address = settings.sink_address
    101 
    102         logg.info('intialized at block {} timestamp {} period {} demurrage level {} sink address {} (first address in keystore)'.format(
    103                 self.last_block,
    104                 self.last_timestamp,
    105                 settings.period_minutes,
    106                 settings.demurrage_level,
    107                 settings.sink_address,
    108                 )
    109             )
    110 
    111         self.eth_helper.disable_auto_mine_transactions()
    112 
    113         self.caller_contract = DemurrageToken(self.chain_spec)
    114         self.caller_address = self.accounts[0]
    115 
    116 
    117     def __check_limit(self):
    118         if self.period_tx_limit == len(self.period_txs):
    119             raise TxLimitException('reached period tx limit {}'.format(self.period_tx_limit))
    120 
    121 
    122     def __check_tx(self, tx_hash):
    123         o = receipt(tx_hash)
    124         rcpt = self.rpc.do(o)
    125         if rcpt['status'] == 0:
    126             raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index']))
    127         logg.debug('tx {} block {} index {} verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
    128 
    129 
    130     def get_now(self):
    131         o = block_latest()
    132         r = self.rpc.do(o)
    133         o = block_by_number(r, include_tx=False)
    134         r = self.rpc.do(o)
    135         return r['timestamp']
    136 
    137 
    138     def get_minutes(self):
    139         t = self.get_now()
    140         return int((t - self.start_timestamp) / 60)
    141 
    142 
    143     def get_start(self):
    144         return self.start_timestamp
    145 
    146 
    147     def get_period(self):
    148         o = self.caller_contract.actual_period(self.address, sender_address=self.caller_address)
    149         r = self.rpc.do(o)
    150         return self.caller_contract.parse_actual_period(r)
    151 
    152 
    153     def get_demurrage(self):
    154         o = self.caller_contract.demurrage_amount(self.address, sender_address=self.caller_address)
    155         r = self.rpc.do(o)
    156         logg.info('demrrage amount {}'.format(r))
    157         return float(self.caller_contract.parse_demurrage_amount(r) / (10 ** 38))
    158 
    159 
    160     def get_supply(self):
    161         o = self.caller_contract.total_supply(self.address, sender_address=self.caller_address)
    162         r = self.rpc.do(o)
    163         supply = self.caller_contract.parse_total_supply(r)
    164         return supply
    165 
    166 
    167     def from_units(self, v):
    168         return v * (10 ** self.decimals)
    169 
    170 
    171     def mint(self, recipient, value):
    172         self.__check_limit()
    173         nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc)
    174         c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
    175         (tx_hash, o) = c.mint_to(self.address, self.accounts[0], recipient, value)
    176         self.rpc.do(o)
    177         self.__next_block()
    178         self.__check_tx(tx_hash)
    179         self.period_txs.append(tx_hash)
    180         logg.info('mint {} tokens to {} - {}'.format(value, recipient, tx_hash))
    181         return tx_hash
    182 
    183 
    184     def transfer(self, sender, recipient, value):
    185         nonce_oracle = RPCNonceOracle(sender, conn=self.rpc)
    186         c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
    187         (tx_hash, o) = c.transfer(self.address, sender, recipient, value)
    188         self.rpc.do(o)
    189         self.__next_block()
    190         self.__check_tx(tx_hash)
    191         self.period_txs.append(tx_hash)
    192         logg.info('transfer {} tokens from {} to {} - {}'.format(value, sender, recipient, tx_hash))
    193         return tx_hash
    194 
    195 
    196     def balance(self, holder, base=False):
    197         o = None
    198         if base:
    199             o = self.caller_contract.base_balance_of(self.address, holder, sender_address=self.caller_address)
    200         else:
    201             o = self.caller_contract.balance_of(self.address, holder, sender_address=self.caller_address)
    202         r = self.rpc.do(o)
    203         return self.caller_contract.parse_balance(r)
    204 
    205 
    206     def __next_block(self):
    207         hsh = self.eth_helper.mine_block()
    208         o = block_by_hash(hsh)
    209         r = self.rpc.do(o)
    210 
    211         for tx_hash in r['transactions']:
    212             o = receipt(tx_hash)
    213             rcpt = self.rpc.do(o)
    214             if rcpt['status'] == 0:
    215                 raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index']))
    216             logg.debug('tx {} (block {} index {}) verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
    217 
    218         logg.debug('now at block {} timestamp {}'.format(r['number'], r['timestamp']))
    219 
    220 
    221     def next(self):
    222         target_timestamp = self.start_timestamp + (self.period * self.period_seconds)
    223         logg.info('warping to {}, {} from start {}'.format(target_timestamp, target_timestamp - self.start_timestamp, self.start_timestamp))
    224         self.last_timestamp = target_timestamp 
    225 
    226         o = block_latest()
    227         r = self.rpc.do(o)
    228         self.last_block = r
    229         o = block_by_number(r)
    230         r = self.rpc.do(o)
    231         cursor_timestamp = r['timestamp'] + 1
    232 
    233         nonce_oracle = RPCNonceOracle(self.accounts[2], conn=self.rpc)
    234         c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
    235 
    236         i = 0
    237         while cursor_timestamp < target_timestamp:
    238             logg.info('mining block on {}'.format(cursor_timestamp))
    239             (tx_hash, o) = c.apply_demurrage(self.address, self.accounts[2])
    240             self.rpc.do(o)
    241             self.eth_helper.time_travel(cursor_timestamp + 60)
    242             self.__next_block()
    243             o = receipt(tx_hash)
    244             r = self.rpc.do(o)
    245             if r['status'] == 0:
    246                 raise RuntimeError('demurrage fast-forward failed on step {} timestamp {} block timestamp {} target {}'.format(i, cursor_timestamp, target_timestamp))
    247             cursor_timestamp += 60*60 # 1 hour
    248             o = block_by_number(r['block_number'])
    249             b = self.rpc.do(o)
    250             logg.info('block mined on timestamp {} (delta {}) block number {}'.format(b['timestamp'], b['timestamp'] - self.start_timestamp, b['number']))
    251             i += 1
    252 
    253         
    254         (tx_hash, o) = c.apply_demurrage(self.address, self.accounts[2])
    255         self.rpc.do(o)
    256 
    257         nonce_oracle = RPCNonceOracle(self.accounts[3], conn=self.rpc)
    258         c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
    259         (tx_hash, o) = c.change_period(self.address, self.accounts[3])
    260         self.rpc.do(o)
    261         self.eth_helper.time_travel(target_timestamp + 1)
    262         self.__next_block()
    263     
    264         o = block_latest()
    265         r = self.rpc.do(o)
    266         o = block_by_number(self.last_block)
    267         r = self.rpc.do(o)
    268         self.last_block = r['number']
    269         block_base = self.last_block
    270         logg.info('block before demurrage execution {} {}'.format(r['timestamp'], r['number']))
    271 
    272         if self.redistribute:
    273             for actor in self.actors:
    274                 nonce_oracle = RPCNonceOracle(actor, conn=self.rpc)
    275                 c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
    276                 (tx_hash, o) = c.apply_redistribution_on_account(self.address, actor, actor)
    277                 self.rpc.do(o)
    278 
    279             nonce_oracle = RPCNonceOracle(self.sink_address, conn=self.rpc)
    280             c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
    281             (tx_hash, o) = c.apply_redistribution_on_account(self.address, self.sink_address, self.sink_address)
    282             self.rpc.do(o)
    283 
    284         self.__next_block()
    285 
    286         o = block_latest()
    287         self.last_block = self.rpc.do(o)
    288 
    289         o = block_by_number(self.last_block)
    290         r = self.rpc.do(o)
    291         for tx_hash in r['transactions']:
    292             o = receipt(tx_hash)
    293             rcpt = self.rpc.do(o)
    294             if rcpt['status'] == 0:
    295                 raise RuntimeError('demurrage step failed on block {}'.format(self.last_block))
    296 
    297         self.last_timestamp = r['timestamp']
    298         logg.debug('next concludes at block {} timestamp {}, {} after start'.format(self.last_block, self.last_timestamp, self.last_timestamp - self.start_timestamp))
    299         self.period += 1
    300         self.period_txs = []
    301 
    302         return (self.last_block, self.last_timestamp)