erc20-demurrage-token

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

commit 4e11f750e879f5229758840dbf5f29ecb1896af5
parent 7bdd18664e8fffa1501298723691bc36792cdb52
Author: nolash <dev@holbrook.no>
Date:   Sun,  6 Jun 2021 05:57:39 +0200

Revert to mine for every tx, add limit test

Diffstat:
Mpython/erc20_demurrage_token/sim/__init__.py | 1+
Apython/erc20_demurrage_token/sim/error.py | 2++
Mpython/erc20_demurrage_token/sim/sim.py | 105++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mpython/erc20_demurrage_token/token.py | 15+++++++++++++++
Mpython/tests/sim/tests_sim.py | 30+++++++++++++++++++++++-------
Apython/tests/sim/tests_sim_limit.py | 39+++++++++++++++++++++++++++++++++++++++
6 files changed, 146 insertions(+), 46 deletions(-)

diff --git a/python/erc20_demurrage_token/sim/__init__.py b/python/erc20_demurrage_token/sim/__init__.py @@ -1 +1,2 @@ from .sim import DemurrageTokenSimulation +from .error import TxLimitException diff --git a/python/erc20_demurrage_token/sim/error.py b/python/erc20_demurrage_token/sim/error.py @@ -0,0 +1,2 @@ +class TxLimitException(RuntimeError): + pass diff --git a/python/erc20_demurrage_token/sim/sim.py b/python/erc20_demurrage_token/sim/sim.py @@ -17,6 +17,7 @@ from chainlib.eth.address import to_checksum_address from chainlib.eth.block import ( block_latest, block_by_number, + block_by_hash, ) from crypto_dev_signer.keystore.dict import DictKeystore from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer @@ -27,6 +28,7 @@ from hexathon import ( # local imports from erc20_demurrage_token import DemurrageToken +from erc20_demurrage_token.sim.error import TxLimitException logg = logging.getLogger(__name__) @@ -42,12 +44,10 @@ class DemurrageTokenSimulation: self.eth_backend = self.eth_helper.backend self.gas_oracle = OverrideGasOracle(limit=100000, price=1) self.rpc = TestRPCConnection(None, self.eth_helper, self.signer) - self.period = 1 for a in self.keystore.list(): self.accounts.append(add_0x(to_checksum_address(a))) settings.sink_address = self.accounts[0] - self.actors = [] for i in range(actors): idx = i % 10 @@ -85,10 +85,17 @@ class DemurrageTokenSimulation: raise RuntimeError('contract deployment failed') self.address = r['contract_address'] + o = c.decimals(self.address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + self.decimals = c.parse_decimals(r) + self.period_seconds = settings.period_minutes * 60 self.last_block += 1 self.last_timestamp += 1 + self.period = 1 + self.period_txs = [] + self.period_tx_limit = self.period_seconds - 1 logg.info('intialized at block {} timestamp {} period {} demurrage level {} sink address {} (first address in keystore)'.format( self.last_block, @@ -102,15 +109,35 @@ class DemurrageTokenSimulation: self.eth_helper.disable_auto_mine_transactions() self.caller_contract = DemurrageToken(self.chain_spec) + self.caller_address = self.accounts[0] + + def __check_limit(self): + if self.period_tx_limit == len(self.period_txs): + raise TxLimitException('reached period tx limit {}'.format(self.period_tx_limit)) + + + def __check_tx(self, tx_hash): + o = receipt(tx_hash) + rcpt = self.rpc.do(o) + if rcpt['status'] == 0: + raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index'])) + logg.debug('tx {} block {} index {} verified'.format(tx_hash, self.last_block, rcpt['transaction_index'])) + + + def from_units(self, v): + return v * (10 ** self.decimals) def mint(self, recipient, value): + self.__check_limit() nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc) c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle) (tx_hash, o) = c.mint_to(self.address, self.accounts[0], recipient, value) self.rpc.do(o) - logg.info('tx hash {}'.format(tx_hash)) + self.next_block() + self.__check_tx(tx_hash) + self.period_txs.append(tx_hash) return tx_hash @@ -119,51 +146,61 @@ class DemurrageTokenSimulation: c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle) (tx_hash, o) = c.transfer(self.address, sender, recipient, value) self.rpc.do(o) + self.next_block() + self.__check_tx(tx_hash) + self.period_txs.append(tx_hash) return tx_hash - def balance(self, holder): - #o = self.caller_contract.balance_of(self.address, holder, sender_address=self.accounts[0]) - c = DemurrageToken(self.chain_spec) - o = c.balance_of(self.address, holder, sender_address=self.accounts[0]) + def balance(self, holder, base=False): + o = None + if base: + o = self.caller_contract.base_balance_of(self.address, holder, sender_address=self.caller_address) + else: + o = self.caller_contract.balance_of(self.address, holder, sender_address=self.caller_address) r = self.rpc.do(o) return self.caller_contract.parse_balance_of(r) + def next_block(self): + hsh = self.eth_helper.mine_block() + o = block_by_hash(hsh) + r = self.rpc.do(o) + logg.info('now at block {} timestamp {}'.format(r['number'], r['timestamp'])) + + def next(self): - target_timestamp = self.period * self.period_seconds - self.last_timestamp = target_timestamp + target_timestamp = self.start_timestamp + (self.period * self.period_seconds) - 1 + logg.debug('warping to {}, {} from start'.format(target_timestamp, target_timestamp - self.start_timestamp)) + self.last_timestamp = target_timestamp + + self.eth_helper.time_travel(self.last_timestamp) + self.next_block() - self.eth_helper.mine_block() - - self.last_timestamp += 1 o = block_by_number(self.last_block) r = self.rpc.do(o) self.last_block = r['number'] block_base = self.last_block - for tx_hash in r['transactions']: - o = receipt(tx_hash) - rcpt = self.rpc.do(o) - if rcpt['status'] == 0: - raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index'])) - logg.info('tx {} (block {} index {}) verified'.format(tx_hash, self.last_block, rcpt['transaction_index'])) - - #self.eth_helper.time_travel(self.start_timestamp + self.last_timestamp) +# for tx_hash in r['transactions']: +# o = receipt(tx_hash) +# rcpt = self.rpc.do(o) +# if rcpt['status'] == 0: +# raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index'])) +# logg.info('tx {} (block {} index {}) verified'.format(tx_hash, self.last_block, rcpt['transaction_index'])) nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc) c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle) (tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0]) self.rpc.do(o) - #self.eth_helper.time_travel(self.start_timestamp + self.last_timestamp + 9) - self.eth_helper.mine_block() - self.last_block += 1 + self.next_block() (tx_hash, o) = c.change_period(self.address, self.accounts[0]) self.rpc.do(o) - #self.eth_helper.time_travel(self.start_timestamp + self.last_timestamp + 10) - self.eth_helper.mine_block() - self.last_block += 1 + self.next_block() + + o = block_latest() + self.last_block = self.rpc.do(o) o = block_by_number(self.last_block) r = self.rpc.do(o) @@ -173,19 +210,9 @@ class DemurrageTokenSimulation: if rcpt['status'] == 0: raise RuntimeError('demurrage step failed on block {}'.format(self.last_block)) -# while True: -# self.eth_helper.mine_block() -# o = block_latest() -# r = self.rpc.do(o) -# self.last_block = r -# -# o = block_by_number(r, include_tx=False) -# r = self.rpc.do(o) -# self.last_block =r['number'] -# if r['timestamp'] == target_timestamp: -# break - - self.last_timestamp += self.last_block - block_base + self.last_timestamp = r['timestamp'] + logg.info('next concludes at block {} timestamp {}, {} after start'.format(self.last_block, self.last_timestamp, self.last_timestamp - self.start_timestamp)) self.period += 1 + self.period_txs = [] return (self.last_block, self.last_timestamp) diff --git a/python/erc20_demurrage_token/token.py b/python/erc20_demurrage_token/token.py @@ -223,6 +223,21 @@ class DemurrageToken(ERC20): return o + def base_balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS): + o = jsonrpc_template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('baseBalanceOf') + enc.typ(ABIContractType.ADDRESS) + enc.address(address) + data = add_0x(enc.get()) + tx = self.template(sender_address, contract_address) + tx = self.set_code(tx, data) + o['params'].append(self.normalize(tx)) + o['params'].append('latest') + return o + + def apply_demurrage(self, contract_address, sender_address): return self.transact_noarg('applyDemurrage', contract_address, sender_address) diff --git a/python/tests/sim/tests_sim.py b/python/tests/sim/tests_sim.py @@ -17,14 +17,13 @@ class TestSim(unittest.TestCase): def setUp(self): self.chain_spec = ChainSpec('evm', 'foochain', 42) - #self.cap = 1000000000 self.cap = 0 settings = DemurrageTokenSettings() settings.name = 'Simulated Demurrage Token' settings.symbol = 'SIM' settings.decimals = 6 - settings.demurrage_level = 50 - settings.period_minutes = 10800 + settings.demurrage_level = 5010590837337300000000000000000000 # equals approx 2% per month + settings.period_minutes = 10800 # 1 week in minutes self.sim = DemurrageTokenSimulation(self.chain_spec, settings, redistribute=True, cap=self.cap, actors=10) @@ -32,7 +31,7 @@ class TestSim(unittest.TestCase): self.sim.mint(self.sim.actors[0], 1024) self.sim.next() balance = self.sim.balance(self.sim.actors[0]) - self.assertEqual(balance, 1024) + self.assertEqual(balance, 1023) def test_transfer(self): @@ -40,21 +39,38 @@ class TestSim(unittest.TestCase): self.sim.transfer(self.sim.actors[0], self.sim.actors[1], 500) self.sim.next() balance = self.sim.balance(self.sim.actors[0]) - self.assertEqual(balance, 524) + self.assertEqual(balance, 523) balance = self.sim.balance(self.sim.actors[1]) - self.assertEqual(balance, 500) + self.assertEqual(balance, 499) def test_more_periods(self): self.sim.mint(self.sim.actors[0], 1024) + self.sim.mint(self.sim.actors[1], 1024) self.sim.next() self.sim.mint(self.sim.actors[0], 1024) self.sim.next() balance = self.sim.balance(self.sim.actors[0]) - self.assertEqual(balance, 2048) + self.assertEqual(balance, 2047) + + + def test_demurrage(self): + self.sim.mint(self.sim.actors[0], self.sim.from_units(100)) + self.sim.mint(self.sim.actors[1], self.sim.from_units(100)) + self.sim.transfer(self.sim.actors[0], self.sim.actors[2], self.sim.from_units(10)) + self.sim.next() + + balance = self.sim.balance(self.sim.actors[0]) + self.assertEqual(balance, 89995500) + + balance = self.sim.balance(self.sim.actors[1]) + self.assertEqual(balance, 99995000) + + balance = self.sim.balance(self.sim.actors[1], base=True) + self.assertEqual(balance, 100000000) if __name__ == '__main__': diff --git a/python/tests/sim/tests_sim_limit.py b/python/tests/sim/tests_sim_limit.py @@ -0,0 +1,39 @@ +# standard imports +import unittest +import logging + +# external imports +from chainlib.chain import ChainSpec + +# local imports +from erc20_demurrage_token import DemurrageTokenSettings +from erc20_demurrage_token.sim import ( + DemurrageTokenSimulation, + TxLimitException, + ) + +logging.basicConfig(level=logging.INFO) +logg = logging.getLogger() + +class TestLimit(unittest.TestCase): + + def setUp(self): + self.chain_spec = ChainSpec('evm', 'foochain', 42) + self.cap = 0 + settings = DemurrageTokenSettings() + settings.name = 'Simulated Demurrage Token' + settings.symbol = 'SIM' + settings.decimals = 6 + settings.demurrage_level = 1 + settings.period_minutes = 1 + self.sim = DemurrageTokenSimulation(self.chain_spec, settings, redistribute=True, cap=self.cap, actors=1) + + + def test_limit(self): + with self.assertRaises(TxLimitException): + for i in range(60): + self.sim.mint(self.sim.actors[0], i) + + +if __name__ == '__main__': + unittest.main()