erc20-demurrage-token

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

commit e47720fa04c2ca6abc2e3329981fc6f2cd20e529
parent 399e24764aee27b277bebd28c2dfc88b6b7098fd
Author: nolash <dev@holbrook.no>
Date:   Mon,  7 Jun 2021 09:04:17 +0200

Add redistribution single minute demurrage test

Diffstat:
Mpython/erc20_demurrage_token/sim/sim.py | 4++--
Mpython/examples/sim_noredistribute.py | 10+++++-----
Mpython/tests/base.py | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Apython/tests/test_redistribution_unit.py | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 198 insertions(+), 7 deletions(-)

diff --git a/python/erc20_demurrage_token/sim/sim.py b/python/erc20_demurrage_token/sim/sim.py @@ -148,10 +148,10 @@ class DemurrageTokenSimulation: def get_period(self): return self.period - def get_demurrage_modifier(self): + def get_demurrage(self): o = self.caller_contract.demurrage_amount(self.address, sender_address=self.caller_address) r = self.rpc.do(o) - return float(self.caller_contract.parse_demurrage_amount(r) / (10 ** 38)) + return float(self.caller_contract.parse_demurrage_amount(r) / (10 ** 40)) def from_units(self, v): diff --git a/python/examples/sim_noredistribute.py b/python/examples/sim_noredistribute.py @@ -16,7 +16,7 @@ settings.name = 'Simulated Demurrage Token' settings.symbol = 'SIM' settings.decimals = 6 settings.demurrage_level = int(decay_per_minute*(10**40)) -settings.period_minutes = 10800 # 1 week in minutes +settings.period_minutes = 1 # 1 week in minutes chain = 'evm:foochain:42' cap = (10 ** 6) * (10 ** 12) @@ -55,15 +55,15 @@ period = sim.get_period() print('start {} now {} period {} minutes passed {}'.format(start, timestamp, period, minutes)) -contract_demurrage = 1 - sim.get_demurrage_modifier() # demurrage in percent (float) -frontend_demurrage = ((1 - decay_per_minute) ** minutes / 100) # corresponding demurrage modifier (float) +contract_demurrage = 1 - sim.get_demurrage() # demurrage in percent (float) +frontend_demurrage = 1.0 - ((1 - decay_per_minute) ** minutes) # corresponding demurrage modifier (float) demurrage_delta = contract_demurrage - frontend_demurrage # difference between demurrage in contract and demurrage calculated in frontend alice_checksum = 50000000 - (50000000 * frontend_demurrage) + (200000000 * frontend_demurrage) # alice's balance calculated with frontend demurrage print("""alice frontend balance {} alice contract balance {} -frontend demurrage {} -contract demurrage {} +frontend demurrage {:f} +contract demurrage {:f} demurrage delta {}""".format( alice_checksum, sim.balance(alice), diff --git a/python/tests/base.py b/python/tests/base.py @@ -163,3 +163,56 @@ class TestDemurrageCap(TestDemurrage): self.deploy(c, self.mode) logg.info('deployed with mode {}'.format(self.mode)) + + + +class TestDemurrageUnit(TestDemurrage): + + def setUp(self): + super(TestDemurrage, self).setUp() + + self.tax_level = 50 + self.period_seconds = 60 + + nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) + self.settings = DemurrageTokenSettings() + self.settings.name = 'Foo Token' + self.settings.symbol = 'FOO' + self.settings.decimals = 6 + self.settings.demurrage_level = self.tax_level * (10 ** 32) + self.settings.period_minutes = int(self.period_seconds/60) + self.settings.sink_address = self.accounts[9] + self.sink_address = self.settings.sink_address + + o = block_latest() + self.start_block = self.rpc.do(o) + + o = block_by_number(self.start_block, include_tx=False) + r = self.rpc.do(o) + + try: + self.start_time = int(r['timestamp'], 16) + except TypeError: + self.start_time = int(r['timestamp']) + + self.default_supply = 1000000000000 + self.default_supply_cap = int(self.default_supply * 10) + + nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) + c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + + self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE') + unit_valid_modes = [ + 'SingleNocap', + 'SingleCap', + ] + if self.mode != None: + if self.mode not in unit_valid_modes: + raise ValueError('Invalid mode "{}" for "unit" contract tests, valid are {}'.format(self.mode, unit_valid_modes)) + else: + self.mode = 'SingleNocap' + logg.debug('executing test setup unit mode {}'.format(self.mode)) + + self.deploy(c, self.mode) + + logg.info('deployed with mode {}'.format(self.mode)) diff --git a/python/tests/test_redistribution_unit.py b/python/tests/test_redistribution_unit.py @@ -0,0 +1,138 @@ +import os +import unittest +import json +import logging + +# external imports +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.tx import receipt +from chainlib.eth.block import ( + block_latest, + block_by_number, + ) +from chainlib.eth.address import to_checksum_address +from hexathon import ( + strip_0x, + add_0x, + ) + +# local imports +from erc20_demurrage_token import DemurrageToken + +# test imports +from tests.base import TestDemurrageUnit + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + +testdir = os.path.dirname(__file__) + + +class TestRedistribution(TestDemurrageUnit): + + def test_single_step(self): + nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) + c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + + mint_amount = 100000000 + + (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], mint_amount) + self.rpc.do(o) + + self.backend.time_travel(self.start_time + self.period_seconds) + + (tx_hash, o) = c.change_period(self.address, self.accounts[0]) + self.rpc.do(o) + + expected_balance = int(mint_amount - ((self.tax_level / 1000000) * mint_amount)) + + o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0]) + r = self.rpc.do(o) + balance = c.parse_balance_of(r) + + self.assertEqual(balance, expected_balance) + + + def test_single_step_multi(self): + nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) + c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + + mint_amount = 100000000 + + for i in range(3): + (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[i+1], mint_amount) + self.rpc.do(o) + + self.backend.time_travel(self.start_time + self.period_seconds) + + (tx_hash, o) = c.change_period(self.address, self.accounts[0]) + self.rpc.do(o) + + expected_balance = int(mint_amount - ((self.tax_level / 1000000) * mint_amount)) + + for i in range(3): + o = c.balance_of(self.address, self.accounts[i+1], sender_address=self.accounts[0]) + r = self.rpc.do(o) + balance = c.parse_balance_of(r) + self.assertEqual(balance, expected_balance) + + + def test_single_step_transfer(self): + nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) + c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + + mint_amount = 100000000 + half_mint_amount = int(mint_amount / 2) + + (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], mint_amount) + self.rpc.do(o) + + (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[2], mint_amount) + self.rpc.do(o) + + nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc) + c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + (tx_hash, o) = c.transfer(self.address, self.accounts[1], self.accounts[3], half_mint_amount) + self.rpc.do(o) + + self.backend.time_travel(self.start_time + self.period_seconds) + + (tx_hash, o) = c.change_period(self.address, self.accounts[1]) + self.rpc.do(o) + + demurrage_amount = int((self.tax_level / 1000000) * mint_amount) + + expected_balance = mint_amount - demurrage_amount + o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0]) + r = self.rpc.do(o) + balance = c.parse_balance_of(r) + self.assertEqual(balance, expected_balance) + + half_demurrage_amount = int((self.tax_level / 1000000) * half_mint_amount) + + expected_balance = half_mint_amount - half_demurrage_amount + o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0]) + r = self.rpc.do(o) + balance = c.parse_balance_of(r) + self.assertEqual(balance, expected_balance) + + o = c.balance_of(self.address, self.accounts[3], sender_address=self.accounts[0]) + r = self.rpc.do(o) + balance = c.parse_balance_of(r) + self.assertEqual(balance, expected_balance) + + o = c.total_supply(self.address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + supply = c.parse_total_supply(r) + + expected_balance = int(supply * (self.tax_level / 1000000)) + + o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + balance = c.parse_balance_of(r) + self.assertEqual(balance, expected_balance) + + +if __name__ == '__main__': + unittest.main()