5 Commits

Author SHA1 Message Date
lash
127c67e665 Add steps option to demurrage cli 2022-05-03 18:19:28 +00:00
lash
1387451e01 Bump deps 2022-04-24 18:53:09 +00:00
lash
3a1fb22631 Remove arg defaults 2022-03-02 13:32:31 +00:00
lash
f1a2a78eb4 Merge branch 'master' into lash/apply-cli 2022-03-02 09:03:40 +00:00
lash
1e24ec1352 Add apply demurrage cli tool 2022-03-02 08:15:10 +00:00
29 changed files with 191 additions and 1154 deletions

5
CAVEAT
View File

@@ -1,5 +0,0 @@
The contract is intended for slow rates of decay (e.g. 2% per month). Very high levels of decay (2% per minute) will lead to overflows, and will need a more flexible implementation to support it.
The contract is written with frequent usage in mind. If used for tokens with low usage freqency (e.g. several days idle), it is recommended to run a continuous process triggering the changePeriod() contract call, to reduce the amount of exponential calculation the application of demurrage will trigger.
When changing the period, the supply for the consecutive period will be taken at the time of code execution, and thus not necessarily at the time when the redistribution period threshold was crossed.

View File

@@ -1,8 +0,0 @@
- 0.1.3
* Snapshot supply for crossed redistribution thresholds before minting new tokens.
- 0.1.4
* Implement natural logarithm
- 0.1.5
* Port changes from SingleNocap to SingleCap
- 0.2.0
* Make decay resolutions configurable, to support high levels of decay.

View File

@@ -1 +0,0 @@
0.1.2

View File

@@ -1,9 +1,3 @@
- 0.2.0
* Add token burn function
* Fix gas leak when calculating decay on period change
* Remove all but SingleNocap contract in make install
* Make approve explicitly set value
* Add increaseAllowance and decreaseAllowance methods
- 0.1.1
* Settable demurrage steps for apply demurrage cli tool
- 0.1.0

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,8 @@ from chainlib.eth.constant import ZERO_ADDRESS
# local imports
from .token import DemurrageToken
logg = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
class DemurrageCalculator:

View File

@@ -16,7 +16,17 @@ import math
# external imports
import confini
import chainlib.eth.cli
from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore
from chainlib.chain import ChainSpec
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
)
from chainlib.eth.gas import (
RPCGasOracle,
OverrideGasOracle,
)
from chainlib.eth.block import (
block_latest,
block_by_number,
@@ -25,20 +35,8 @@ from chainlib.eth.block import (
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import receipt
from chainlib.eth.constant import ZERO_ADDRESS
from hexathon import to_int as hex_to_int
import chainlib.eth.cli
from chainlib.eth.settings import process_settings
from chainlib.settings import ChainSettings
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
from hexathon import to_int as hex_to_int
# local imports
import erc20_demurrage_token
@@ -47,38 +45,32 @@ from erc20_demurrage_token import (
DemurrageTokenSettings,
)
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(__file__)
data_dir = os.path.join(script_dir, '..', 'data')
def process_config_local(config, arg, args, flags):
config.add(args.steps, '_STEPS', False)
return config
config_dir = os.path.join(data_dir, 'config')
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_WRITE | arg_flags.EXEC | arg_flags.WALLET
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('--steps', type=int, default=0, help='Max demurrage steps to apply per round')
args = argparser.parse_args()
logg = process_log(args, logg)
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_fee_limit=DemurrageToken.gas(), base_config_dir=config_dir)
config.add(args.steps, '_STEPS', False)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
wallet = chainlib.eth.cli.Wallet()
wallet.from_config(config)
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
def main():
chain_spec = settings.get('CHAIN_SPEC')
conn = settings.get('CONN')
o = block_latest()
r = conn.do(o)
@@ -95,9 +87,9 @@ def main():
block_start_timestamp = block_start.timestamp
block_start_datetime = datetime.datetime.fromtimestamp(block_start_timestamp)
gas_oracle = settings.get('FEE_ORACLE')
gas_oracle = rpc.get_gas_oracle()
c = DemurrageToken(chain_spec, gas_oracle=gas_oracle)
o = c.demurrage_timestamp(settings.get('EXEC'))
o = c.demurrage_timestamp(config.get('_EXEC_ADDRESS'))
r = conn.do(o)
demurrage_timestamp = None
@@ -128,17 +120,17 @@ def main():
last_tx_hash = None
for i in range(rounds):
signer = settings.get('SIGNER')
signer_address = settings.get('SENDER_ADDRESS')
signer = rpc.get_signer()
signer_address = rpc.get_sender_address()
nonce_oracle = settings.get('NONCE_ORACLE')
nonce_oracle = rpc.get_nonce_oracle()
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
(tx_hash_hex, o) = c.apply_demurrage(config.get('_EXEC_ADDRESS'), signer_address, limit=config.get('_STEPS'))
if settings.get('RPC_SEND'):
if config.get('_RPC_SEND'):
print(tx_hash_hex)
conn.do(o)
if config.true('_WAIT_ALL') or (i == rounds - 1 and config.true('_WAIT')):
if config.get('_WAIT_ALL') or (i == rounds - 1 and config.get('_WAIT')):
r = conn.wait(tx_hash_hex)
if r['status'] == 0:
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')

View File

@@ -14,22 +14,21 @@ import logging
# external imports
import confini
from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore
from chainlib.chain import ChainSpec
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
)
from chainlib.eth.gas import (
RPCGasOracle,
OverrideGasOracle,
)
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import receipt
from chainlib.eth.constant import ZERO_ADDRESS
import chainlib.eth.cli
from chainlib.eth.settings import process_settings
from chainlib.settings import ChainSettings
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
# local imports
import erc20_demurrage_token
@@ -38,75 +37,84 @@ from erc20_demurrage_token import (
DemurrageTokenSettings,
)
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(__file__)
data_dir = os.path.join(script_dir, '..', 'data')
def process_config_local(config, arg, args, flags):
config.add(args.token_name, 'TOKEN_NAME', False)
config.add(args.token_symbol, 'TOKEN_SYMBOL', False)
config.add(args.token_decimals, 'TOKEN_DECIMALS', False)
config.add(args.sink_address, 'TOKEN_SINK_ADDRESS', False)
config.add(args.redistribution_period, 'TOKEN_REDISTRIBUTION_PERIOD', False)
config.add(args.demurrage_level, 'TOKEN_DEMURRAGE_LEVEL', False)
config.add(0, 'TOKEN_SUPPLY_LIMIT', False)
return config
config_dir = os.path.join(data_dir, 'config')
arg_flags = ArgFlag()
arg = Arg(arg_flags)
flags = arg_flags.STD_WRITE | arg_flags.EXEC | arg_flags.WALLET
argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags)
arg_flags = chainlib.eth.cli.argflag_std_write
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('--name', dest='token_name', type=str, help='Token name')
argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol')
argparser.add_argument('--decimals', dest='token_decimals', type=int, help='Token decimals')
argparser.add_argument('--sink-address', dest='sink_address', type=str, help='demurrage level,ppm per minute')
#argparser.add_argument('--supply-limit', dest='supply_limit', type=int, help='token supply limit (0 = no limit)')
argparser.add_argument('--redistribution-period', dest='redistribution_period', type=int, help='redistribution period, minutes (0 = deactivate)') # default 10080 = week
#argparser.add_argument('--multi', action='store_true', help='automatic redistribution')
argparser.add_argument('--supply-limit', dest='supply_limit', type=int, help='token supply limit (0 = no limit)')
argparser.add_argument('--redistribution-period', type=int, help='redistribution period, minutes (0 = deactivate)') # default 10080 = week
argparser.add_argument('--multi', action='store_true', help='automatic redistribution')
argparser.add_argument('--demurrage-level', dest='demurrage_level', type=int, help='demurrage level, ppm per minute')
args = argparser.parse_args()
logg = process_log(args, logg)
arg_flags = chainlib.eth.cli.argflag_std_write
config = Config()
config = process_config(config, arg, args, flags)
config = process_config_local(config, arg, args, flags)
extra_args = {
'redistribution_period': 'TOKEN_REDISTRIBUTION_PERIOD',
'demurrage_level': 'TOKEN_DEMURRAGE_LEVEL',
'supply_limit': 'TOKEN_SUPPLY_LIMIT',
'token_name': 'TOKEN_NAME',
'token_symbol': 'TOKEN_SYMBOL',
'token_decimals': 'TOKEN_DECIMALS',
'sink_address': 'TOKEN_SINK_ADDRESS',
'multi': None,
}
config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_fee_limit=DemurrageToken.gas(), base_config_dir=config_dir)
if not bool(config.get('TOKEN_NAME')):
logg.info('token name not set, using symbol {} as name'.format(config.get('TOKEN_SYMBOL')))
config.add(config.get('TOKEN_SYMBOL'), 'TOKEN_NAME', True)
if config.get('TOKEN_SUPPLY_LIMIT') == None:
config.add(0, 'TOKEN_SUPPLY_LIMIT', True)
if config.get('TOKEN_REDISTRIBUTION_PERIOD') == None:
config.add(10800, 'TOKEN_REDISTRIBUTION_PERIOD', True)
logg.debug('config loaded:\n{}'.format(config))
settings = ChainSettings()
settings = process_settings(settings, config)
logg.debug('settings loaded:\n{}'.format(settings))
wallet = chainlib.eth.cli.Wallet()
wallet.from_config(config)
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
def main():
chain_spec = settings.get('CHAIN_SPEC')
conn = settings.get('CONN')
signer = settings.get('SIGNER')
signer_address = settings.get('SENDER_ADDRESS')
signer = rpc.get_signer()
signer_address = rpc.get_sender_address()
gas_oracle = settings.get('FEE_ORACLE')
nonce_oracle = settings.get('NONCE_ORACLE')
gas_oracle = rpc.get_gas_oracle()
nonce_oracle = rpc.get_nonce_oracle()
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
token_settings = DemurrageTokenSettings()
token_settings.name = config.get('TOKEN_NAME')
token_settings.symbol = config.get('TOKEN_SYMBOL')
token_settings.decimals = int(config.get('TOKEN_DECIMALS'))
token_settings.demurrage_level = int(config.get('TOKEN_DEMURRAGE_LEVEL'))
token_settings.period_minutes = int(config.get('TOKEN_REDISTRIBUTION_PERIOD'))
token_settings.sink_address = config.get('TOKEN_SINK_ADDRESS')
settings = DemurrageTokenSettings()
settings.name = config.get('TOKEN_NAME')
settings.symbol = config.get('TOKEN_SYMBOL')
settings.decimals = int(config.get('TOKEN_DECIMALS'))
settings.demurrage_level = int(config.get('TOKEN_DEMURRAGE_LEVEL'))
settings.period_minutes = int(config.get('TOKEN_REDISTRIBUTION_PERIOD'))
settings.sink_address = config.get('TOKEN_SINK_ADDRESS')
(tx_hash_hex, o) = c.constructor(
signer_address,
token_settings,
settings,
redistribute=config.true('_MULTI'),
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
)
if settings.get('RPC_SEND'):
if config.get('_RPC_SEND'):
conn.do(o)
if config.true('_WAIT'):
if config.get('_WAIT'):
r = conn.wait(tx_hash_hex)
if r['status'] == 0:
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')

View File

@@ -38,15 +38,6 @@ class DemurrageTokenSettings:
self.sink_address = None
def __str__(self):
return 'name {} demurrage level {} period minutes {} sink address {}'.format(
self.name,
self.demurrage_level,
self.period_minutes,
self.sink_address,
)
class DemurrageToken(ERC20):
__abi = {}
@@ -117,34 +108,6 @@ class DemurrageToken(ERC20):
return DemurrageToken.__bytecode[name]
def increase_allowance(self, contract_address, sender_address, address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('increaseAllowance')
enc.typ(ABIContractType.ADDRESS)
enc.typ(ABIContractType.UINT256)
enc.address(address)
enc.uint256(value)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def decrease_allowance(self, contract_address, sender_address, address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('decreaseAllowance')
enc.typ(ABIContractType.ADDRESS)
enc.typ(ABIContractType.UINT256)
enc.address(address)
enc.uint256(value)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def add_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('addMinter')
@@ -183,33 +146,6 @@ class DemurrageToken(ERC20):
return tx
def burn(self, contract_address, sender_address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('burn')
enc.typ(ABIContractType.UINT256)
enc.uint256(value)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def total_burned(self, contract_address, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('totalBurned')
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')
o = j.finalize(o)
return o
def to_base_amount(self, contract_address, value, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
@@ -581,7 +517,6 @@ class DemurrageToken(ERC20):
def parse_supply_cap(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_grow_by(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@@ -605,8 +540,3 @@ class DemurrageToken(ERC20):
@classmethod
def parse_resolution_factor(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_total_burned(self, v):
return abi_decode_single(ABIContractType.UINT256, v)

View File

@@ -20,7 +20,7 @@ from erc20_demurrage_token import (
DemurrageToken,
)
logg = logging.getLogger(__name__)
logg = logging.getLogger()
#BLOCKTIME = 5 # seconds
TAX_LEVEL = int(10000 * 2) # 2%
@@ -32,19 +32,18 @@ PERIOD = 10
class TestTokenDeploy:
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, supply=10**12, tax_level=TAX_LEVEL, period=PERIOD):
self.tax_level = tax_level
self.period_seconds = period * 60
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, supply=10**12):
self.tax_level = TAX_LEVEL
self.period_seconds = PERIOD * 60
self.settings = DemurrageTokenSettings()
self.settings.name = token_name
self.settings.symbol = token_symbol
self.settings.decimals = 6
self.settings.demurrage_level = tax_level * (10 ** 32)
self.settings.period_minutes = period
self.settings.demurrage_level = TAX_LEVEL * (10 ** 32)
self.settings.period_minutes = PERIOD
self.settings.sink_address = sink_address
self.sink_address = self.settings.sink_address
logg.debug('using demurrage token settings: {}'.format(self.settings))
o = block_latest()
self.start_block = rpc.do(o)
@@ -95,12 +94,14 @@ class TestDemurrage(EthTesterCase):
def setUp(self):
super(TestDemurrage, self).setUp()
period = PERIOD
try:
period = getattr(self, 'period')
except AttributeError as e:
pass
self.deployer = TestTokenDeploy(self.rpc, period=period)
# token_deploy = TestTokenDeploy()
# self.settings = token_deploy.settings
# self.sink_address = token_deploy.sink_address
# self.start_block = token_deploy.start_block
# self.start_time = token_deploy.start_time
# self.default_supply = self.default_supply
# self.default_supply_cap = self.default_supply_cap
self.deployer = TestTokenDeploy(self.rpc)
self.default_supply = self.deployer.default_supply
self.default_supply_cap = self.deployer.default_supply_cap
self.start_block = None
@@ -126,13 +127,6 @@ class TestDemurrage(EthTesterCase):
logg.debug('asserted within lower {} <= {} <= {}'.format(lower_target, v, target))
def assert_within_greater(self, v, target, tolerance_ppm):
higher_target = target + (target * (tolerance_ppm / 1000000))
self.assertLessEqual(v, higher_target)
self.assertGreaterEqual(v, target)
logg.debug('asserted within lower {} <= {} <= {}'.format(target, v, higher_target))
def tearDown(self):
pass
@@ -209,11 +203,10 @@ class TestDemurrageCap(TestDemurrage):
class TestDemurrageUnit(TestDemurrage):
def setUp(self):
self.period = 1
self.period_seconds = self.period * 60
self.tax_level = TAX_LEVEL
super(TestDemurrage, self).setUp()
super(TestDemurrageUnit, self).setUp()
self.tax_level = 50
self.period_seconds = 60
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
self.settings = DemurrageTokenSettings()

View File

@@ -1,3 +1,3 @@
chainlib-eth~=0.4.6
eth-erc20~=0.5.1
chainlib-eth>=0.1.0,<0.2.0
eth-erc20~=0.3.0
funga-eth~=0.6.0

View File

@@ -6,30 +6,21 @@ set -e
export PYTHONPATH=.
#modes=(MultiNocap MultiCap SingleCap SingleNocap)
#modes=(SingleCap SingleNocap) # other contracts need to be updted
modes=(SingleNocap) # other contracts need to be updted
modes=(SingleCap SingleNocap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_basic.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_growth.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_amounts.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_single.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_burn.py
done
#modes=(SingleCap) # other contracts need to be updted
modes=()
modes=(SingleCap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_period.py
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
done
modes=(SingleNocap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_single.py
done
#modes=(MultiCap SingleCap)
modes=()
modes=(MultiCap SingleCap)
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_cap.py
done

View File

@@ -1,6 +1,6 @@
[metadata]
name = erc20-demurrage-token
version = 0.2.0
version = 0.1.1
description = ERC20 token with redistributed continual demurrage
author = Louis Holbrook
author_email = dev@holbrook.no

View File

@@ -276,52 +276,7 @@ class TestBasic(TestDemurrageDefault):
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
def test_approve(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 500)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 600)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 0)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 600)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.increase_allowance(self.address, self.accounts[0], self.accounts[1], 200)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.decrease_allowance(self.address, self.accounts[0], self.accounts[1], 800)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 42)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
def test_transfer_from(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
@@ -360,12 +315,6 @@ class TestBasic(TestDemurrageDefault):
balance = c.parse_balance_of(r)
self.assertEqual(balance, 500)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[2], self.accounts[1], self.accounts[3], 1)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,320 +0,0 @@
# standard imports
import os
import unittest
import json
import logging
import datetime
# 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,
)
# local imports
from erc20_demurrage_token import DemurrageToken
# test imports
from erc20_demurrage_token.unittest.base import TestDemurrage
logging.basicConfig(level=logging.INFO)
logg = logging.getLogger()
testdir = os.path.dirname(__file__)
TAX_LEVEL = 2
class TestBurn(TestDemurrage):
def setUp(self):
super(TestBurn, self).setUp()
def deploy(self, tax_level=None):
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')
if self.mode == None:
self.mode = 'MultiNocap'
logg.debug('executing test setup default mode {}'.format(self.mode))
if tax_level != None:
self.deployer.settings.demurrage_level = tax_level * (10 ** 32)
self.deployer.settings.sink_address = self.accounts[9]
self.deployer.sink_address = self.accounts[9]
super(TestBurn, self).deploy(c, self.mode)
logg.info('deployed with mode {}'.format(self.mode))
# Burn tokens and immediately check balances and supply
def test_burn_basic(self):
self.deploy()
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1000000)
r = 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.burn(self.address, self.accounts[1], 600000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.add_minter(self.address, self.accounts[0], self.accounts[1])
r = 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.burn(self.address, self.accounts[1], 600000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
self.assertEqual(new_supply, 400000)
o = c.total_burned(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
burned = c.parse_total_burned(r)
self.assertEqual(burned, 600000)
# burn tokens and check sink balance and supply after first redistribution period
def test_burned_redistribution(self):
self.deploy()
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], 1000000000)
r = self.rpc.do(o)
(tx_hash, o) = c.burn(self.address, self.accounts[0], 500000000)
r = self.rpc.do(o)
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.sink_address, 500000000)
r = self.rpc.do(o)
self.backend.time_travel(self.start_time + self.period_seconds)
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
bal = c.parse_balance(r)
self.assertEqual(bal, 416873881) # 9 periods demurrage
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
self.assertEqual(new_supply, 500000000)
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
bal = c.parse_balance(r)
self.assert_within_lower(bal, 500000000, 0.0025)
self.backend.time_travel(self.start_time + (self.period_seconds * 2))
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
self.assertEqual(new_supply, 500000000)
# if we don't burn anything more it should be the same
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
bal = c.parse_balance(r)
self.assert_within_lower(bal, 500000000, 0.0025)
# burn tokens and check sink and taxed balance and supply after first redistribution period
def test_burned_other_redistribution(self):
self.deploy()
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], 1000000000)
r = self.rpc.do(o)
(tx_hash, o) = c.burn(self.address, self.accounts[0], 500000000)
r = self.rpc.do(o)
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.accounts[1], 500000000)
r = self.rpc.do(o)
self.backend.time_travel(self.start_time + self.period_seconds)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bal = c.parse_balance(r)
self.assertEqual(bal, 416873881) # 9 periods demurrage
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
self.assertEqual(new_supply, 500000000)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bal = c.parse_balance(r)
self.assertEqual(bal, 408536403) # 9 periods demurrage
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
sink_bal = c.parse_balance(r)
self.assert_within_lower(sink_bal, 500000000 - 408536403, 0.09) # TODO is this ok variance, 1.0 is ppm?
self.backend.time_travel(self.start_time + (self.period_seconds * 2))
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
self.assertEqual(new_supply, 500000000)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
next_bal = c.parse_balance(r)
self.assertEqual(next_bal, 333803985) # 9 periods demurrage
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
prev_sink_bal = sink_bal
bal = prev_sink_bal + (bal - next_bal)
sink_bal = c.parse_balance(r)
self.assert_within_lower(sink_bal, bal, 0.09) # TODO is this ok variance, 1.0 is ppm?
# verify expected results of balance and supply after multiple redistribution periods
def test_burn_accumulate(self):
self.deploy(tax_level=2/1000)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.add_minter(self.address, self.accounts[0], self.sink_address)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.sink_address, self.default_supply)
r = self.rpc.do(o)
balance_share = int(self.default_supply / 2)
nonce_oracle = RPCNonceOracle(self.sink_address, self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.transfer(self.address, self.sink_address, self.accounts[1], balance_share)
r = self.rpc.do(o)
new_supply = None
burn_rate = 1000
sink_bal = None
bob_bal = None
bob_refund = None
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bob_bal = c.parse_balance(r)
prev_bob_bal = bob_bal
iterations = 100
for i in range(1, iterations + 1):
nonce_oracle = RPCNonceOracle(self.sink_address, self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
if bob_refund != None:
(tx_hash, o) = c.transfer(self.address, self.sink_address, self.accounts[1], bob_refund)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.burn(self.address, self.sink_address, burn_rate)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
self.backend.time_travel(self.start_time + (self.period_seconds * i))
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
self.rpc.do(o)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bob_bal = c.parse_balance(r)
bob_refund = prev_bob_bal - bob_bal
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
burner_bal = c.parse_balance(r)
sum_supply = bob_bal + burner_bal
o = c.total_burned(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
total_burned = c.parse_balance(r)
o = c.to_base_amount(self.address, total_burned, sender_address=self.accounts[0])
r = self.rpc.do(o)
total_burned_base = c.parse_balance(r)
expected_supply = self.default_supply - (burn_rate * i)
logg.info('checking burn round {} balance burner {} bob {} supply {} expected {} summed {} burned {} base {}'.format(i, burner_bal, bob_bal, new_supply, expected_supply, sum_supply, total_burned, total_burned_base))
self.assertEqual(new_supply, expected_supply)
sum_supply = burner_bal + bob_bal
logg.debug('balances sink {} bob {} total {} supply real {} original {}'.format(sink_bal, bob_bal, sum_supply, new_supply, self.default_supply))
self.assert_within_lower(sum_supply, new_supply, 0.00001)
self.assert_within_greater(burner_bal, balance_share - total_burned, 0.1)
bob_delta = self.default_supply * ((2 / 1000000) / 1000)
self.assert_within_lower(bob_bal, balance_share - bob_delta, 0.1)
self.assertEqual(total_burned, iterations * burn_rate)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,133 +0,0 @@
# standard imports
import datetime
import unittest
import logging
import os
# external imports
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import receipt
# local imports
from erc20_demurrage_token import DemurrageToken
from erc20_demurrage_token.demurrage import DemurrageCalculator
# test imports
from erc20_demurrage_token.unittest.base import TestDemurrage
logging.basicConfig(level=logging.INFO)
logg = logging.getLogger()
class TestDemurragePeriods(TestDemurrage):
def setUp(self):
super(TestDemurragePeriods, self).setUp()
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')
if self.mode == None:
self.mode = 'MultiNocap'
logg.debug('executing test setup default mode {}'.format(self.mode))
self.deployer.settings.demurrage_level = (2 / 1000) * (10 ** 32)
self.deployer.settings.sink_address = self.accounts[9]
self.deployer.sink_address = self.accounts[9]
self.deploy(c, self.mode)
logg.info('deployed with mode {}'.format(self.mode))
# verify that tax level calculation is in ppm as expected
def test_ppm(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], self.default_supply)
r = self.rpc.do(o)
self.backend.time_travel(self.start_time + 60)
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bob_bal = c.parse_balance(r)
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
sink_bal = c.parse_balance(r)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
balance_delta = self.default_supply * ((2 / 1000000) / 1000)
self.assertEqual(bob_bal, self.default_supply - balance_delta)
# verify balances and supply after multiple demurrage periods
def test_over_time(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], self.default_supply)
r = self.rpc.do(o)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bob_bal = c.parse_balance(r)
prev_bob_bal = bob_bal
nonce_oracle = RPCNonceOracle(self.sink_address, self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
iterations = 100
for i in range(1, iterations + 1):
self.backend.time_travel(self.start_time + (self.period_seconds * i))
(tx_hash, o) = c.transfer(self.address, self.sink_address, self.accounts[1], prev_bob_bal - bob_bal)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.apply_demurrage(self.address, self.sink_address)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
bob_bal = c.parse_balance(r)
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
sink_bal = c.parse_balance(r)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
new_supply = c.parse_total_supply(r)
logg.info('round {} supply {} balance sink {} bob {}'.format(i, new_supply, sink_bal, bob_bal))
sum_supply = sink_bal + bob_bal
bob_delta = self.default_supply * ((2 / 1000000) / 100)
self.assert_within_lower(sum_supply, new_supply, 0.00001)
self.assert_within_greater(bob_bal, self.default_supply - bob_delta, 0.001)
self.assert_within_lower(sink_bal, bob_delta, 1000)
if __name__ == '__main__':
unittest.main()

View File

@@ -31,8 +31,6 @@ testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageDefault):
def test_whole_is_parts(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)

View File

@@ -1,198 +0,0 @@
# standard imports
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 erc20_demurrage_token.unittest.base import TestDemurrageDefault
logging.basicConfig(level=logging.INFO)
logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageDefault):
def test_redistribution_periods(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
supply = self.default_supply
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], supply)
self.rpc.do(o)
for i in range(1, 10):
logg.debug('execute time travel to period {}'.format(i))
self.backend.time_travel(self.start_time + (self.period_seconds * i))
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.redistributions(self.address, i, sender_address=self.accounts[0])
redistribution = self.rpc.do(o)
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage = c.parse_to_redistribution_item(r)
o = c.redistributions(self.address, i-1, sender_address=self.accounts[0])
redistribution = self.rpc.do(o)
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage_previous = c.parse_to_redistribution_item(r)
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance_sink = c.parse_balance(r)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance_minter = c.parse_balance(r)
logg.debug('testing sink {} mint {} adds up to supply {} with demurrage between {} and {}'.format(balance_sink, balance_minter, supply, demurrage_previous, demurrage))
self.assert_within_lower(balance_minter + balance_sink, supply, 0.001)
def test_redistribution_catchup_periods(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
supply = self.default_supply
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], supply)
self.rpc.do(o)
self.backend.time_travel(self.start_time + (self.period_seconds * 100))
balance_minter = None
balance_sink = None
real_supply = None
for i in range(1, 101):
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance_sink = c.parse_balance(r)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance_minter = c.parse_balance(r)
real_supply = balance_sink + balance_minter
logg.info('period {} testing sink {} mint {} adds up to supply {} of original {} (delta {})'.format(i, balance_sink, balance_minter, real_supply, supply, supply - real_supply))
i = 100
o = c.redistributions(self.address, i, sender_address=self.accounts[0])
redistribution = self.rpc.do(o)
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage = c.parse_to_redistribution_item(r)
o = c.redistributions(self.address, i-1, sender_address=self.accounts[0])
redistribution = self.rpc.do(o)
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage_previous = c.parse_to_redistribution_item(r)
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance_sink = c.parse_balance(r)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance_minter = c.parse_balance(r)
logg.debug('testing sink {} mint {} adds up to supply {} with demurrage between {} and {}'.format(balance_sink, balance_minter, real_supply, demurrage_previous, demurrage))
self.assert_within_lower(balance_minter + balance_sink, supply, 0.001)
# def test_redistribution_boundaries(self):
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
#
# demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
# supply = self.default_supply
#
# (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], supply)
# self.rpc.do(o)
#
# o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# balance = c.parse_balance(r)
# logg.debug('balance before {} supply {}'.format(balance, supply))
#
# self.backend.time_travel(self.start_time + self.period_seconds)
# (tx_hash, o) = c.change_period(self.address, self.accounts[0])
# r = self.rpc.do(o)
#
# o = receipt(tx_hash)
# r = self.rpc.do(o)
# self.assertEqual(r['status'], 1)
#
# o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# oo = c.to_redistribution_supply(self.address, r, sender_address=self.accounts[0])
# rr = self.rpc.do(oo)
# oo = c.to_redistribution_demurrage_modifier(self.address, r, sender_address=self.accounts[0])
# rr = self.rpc.do(oo)
#
# o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# balance = c.parse_balance(r)
#
# self.backend.time_travel(self.start_time + self.period_seconds * 2 + 1)
# (tx_hash, o) = c.change_period(self.address, self.accounts[0])
# r = self.rpc.do(o)
#
# o = receipt(tx_hash)
# r = self.rpc.do(o)
# self.assertEqual(r['status'], 1)
#
# o = c.redistributions(self.address, 2, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# oo = c.to_redistribution_supply(self.address, r, sender_address=self.accounts[0])
# rr = self.rpc.do(oo)
# oo = c.to_redistribution_demurrage_modifier(self.address, r, sender_address=self.accounts[0])
# rr = self.rpc.do(oo)
#
# o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# balance = c.parse_balance(r)
if __name__ == '__main__':
unittest.main()

View File

@@ -31,12 +31,12 @@ testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageUnit):
# TODO: move to "pure" test file when getdistribution is implemented in all contracts
def test_distribution_direct(self):
def test_distribution(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
#demurrage = (1 - (self.tax_level / 1000000)) * (10**38)
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
supply = self.default_supply
@@ -51,23 +51,21 @@ class TestRedistribution(TestDemurrageUnit):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
demurrage = (1 - (self.tax_level / 100000)) * (10**28)
logg.debug('demurrage {}'.format(demurrage))
#demurrage = (1 - (self.tax_level / 1000000)) * (10**38)
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
supply = self.default_supply
o = c.to_redistribution(self.address, 0, demurrage, supply, 2, sender_address=self.accounts[0])
o = c.to_redistribution(self.address, 0, demurrage, supply, 1, sender_address=self.accounts[0])
redistribution = self.rpc.do(o)
o = c.get_distribution_from_redistribution(self.address, redistribution, self.accounts[0])
r = self.rpc.do(o)
distribution = c.parse_get_distribution(r)
expected_distribution = (self.default_supply * self.tax_level) / 100000
logg.debug('distribution {} supply {}'.format(distribution, self.default_supply))
expected_distribution = self.default_supply * (self.tax_level / 1000000)
self.assert_within_lower(distribution, expected_distribution, 1000)
def test_single_step_basic(self):
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)
@@ -83,12 +81,6 @@ class TestRedistribution(TestDemurrageUnit):
expected_balance = int(mint_amount - ((self.tax_level / 1000000) * mint_amount))
o = c.balance_of(self.address, ZERO_ADDRESS, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
logg.debug('balance {}'.format(balance))
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
@@ -183,6 +175,9 @@ class TestRedistribution(TestDemurrageUnit):
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage = c.parse_to_redistribution_item(r)
# o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# demurrage = c.parse_demurrage_amount(r)
logg.debug('\nrediistribution {}\ndemurrage {}\nsupply {}'.format(redistribution, demurrage, supply))
expected_balance = int(supply * (self.tax_level / 1000000))

View File

@@ -90,7 +90,6 @@ class TestRedistributionSingle(TestDemurrageSingle):
r = self.rpc.do(o)
balance = c.parse_balance(r)
expected_balance = new_supply - (new_supply * tax_modifier)
logg.debug('expected balance {}'.format(expected_balance))
self.assert_within_lower(balance, expected_balance, 1)

View File

@@ -1,6 +1,7 @@
pragma solidity >= 0.8.0;
pragma solidity > 0.6.11;
// SPDX-License-Identifier: GPL-3.0-or-later
contract DemurrageTokenSingleCap {
// Redistribution bit field, with associated shifts and masks
@@ -10,28 +11,17 @@ contract DemurrageTokenSingleCap {
uint256 constant maskRedistributionPeriod = 0x00000000000000000000000000000000000000000000000000000000ffffffff; // (1 << 32) - 1
uint8 constant shiftRedistributionValue = 32;
uint256 constant maskRedistributionValue = 0x00000000000000000000000000000000000000ffffffffffffffffff00000000; // ((1 << 72) - 1) << 32
uint8 constant shiftRedistributionParticipants = 104;
uint256 constant maskRedistributionParticipants = 0x00000000000000000000000000000fffffffff00000000000000000000000000; // ((1 << 36) - 1) << 104
uint8 constant shiftRedistributionDemurrage = 140;
uint256 constant maskRedistributionDemurrage = 0x000000000000000000000000fffff00000000000000000000000000000000000; // ((1 << 20) - 1) << 140
//uint8 constant shiftRedistributionDemurrage = 104;
//uint256 constant maskRedistributionDemurrage = 0x0000000000ffffffffffffffffffffffffffff00000000000000000000000000; // ((1 << 36) - 1) << 140
// Account bit field, with associated shifts and masks
// Mirrors structure of redistributions for consistency
mapping (address => bytes32) account; // uint152(unused) | uint32(period) | uint72(value)
uint8 constant shiftAccountValue = 0;
uint256 constant maskAccountValue = 0x0000000000000000000000000000000000000000000000ffffffffffffffffff; // (1 << 72) - 1
uint8 constant shiftAccountPeriod = 72;
uint256 constant maskAccountPeriod = 0x00000000000000000000000000000000000000ffffffff000000000000000000; // ((1 << 32) - 1) << 72
uint8 constant shiftRedistributionDemurrage = 104;
uint256 constant maskRedistributionDemurrage = 0x0000000000ffffffffffffffffffffffffffff00000000000000000000000000; // ((1 << 20) - 1) << 140
// Account balances
//mapping (address => uint256) account;
mapping (address => uint256) account;
// Cached demurrage amount, ppm with 38 digit resolution
uint128 public demurrageAmount;
// Cached demurrage period; the period for which demurrageAmount was calculated
//uint128 public demurragePeriod;
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
uint256 public demurrageTimestamp;
@@ -50,17 +40,10 @@ contract DemurrageTokenSingleCap {
uint256 public decimals;
// Implements ERC20
//uint256 public totalSupply;
uint256 supply;
uint256 public totalSupply;
// Last executed period
uint256 public lastPeriod;
// Last sink redistribution amount
uint256 public totalSink;
// Value of burnt tokens (burnt tokens do not decay)
uint256 public burned;
// Minimum amount of (demurraged) tokens an account must spend to participate in redistribution for a particular period
uint256 public minimumParticipantSpend;
// 128 bit resolution of the demurrage divisor
// (this constant x 1000000 is contained within 128 bits)
@@ -88,7 +71,7 @@ contract DemurrageTokenSingleCap {
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
// Address to send unallocated redistribution tokens
address public sinkAddress;
address sinkAddress;
// Implements ERC20
event Transfer(address indexed _from, address indexed _to, uint256 _value);
@@ -111,9 +94,6 @@ contract DemurrageTokenSingleCap {
// Temporary event used in development, will be removed on prod
event Debug(bytes32 _foo);
// Emitted when tokens are burned
event Burn(address indexed _burner, uint256 _value);
// EIP173
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
@@ -131,13 +111,17 @@ contract DemurrageTokenSingleCap {
demurrageTimestamp = block.timestamp;
periodStart = demurrageTimestamp;
periodDuration = _periodMinutes * 60;
demurrageAmount = uint128(nanoDivider) * 100;
//demurrageAmount = 100000000000000000000000000000000000000 - _taxLevelMinute; // Represents 38 decimal places, same as resolutionFactor
//demurrageAmount = 100000000000000000000000000000000000000;
demurrageAmount = 10000000000000000000000000000;
//demurragePeriod = 1;
taxLevel = _taxLevelMinute; // Represents 38 decimal places
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
redistributions.push(initialRedistribution);
// Misc settings
sinkAddress = _defaultSinkAddress;
minimumParticipantSpend = 10 ** uint256(_decimals);
}
@@ -161,36 +145,6 @@ contract DemurrageTokenSingleCap {
return true;
}
// Deserialize the pemurrage period for the given account is participating in
function accountPeriod(address _account) public view returns (uint256) {
return (uint256(account[_account]) & maskAccountPeriod) >> shiftAccountPeriod;
}
// Add number of participants for the current redistribution period by one
function incrementRedistributionParticipants() private returns (bool) {
bytes32 currentRedistribution;
uint256 tmpRedistribution;
uint256 participants;
currentRedistribution = redistributions[redistributions.length-1];
participants = toRedistributionParticipants(currentRedistribution) + 1;
tmpRedistribution = uint256(currentRedistribution);
tmpRedistribution &= (~maskRedistributionParticipants);
tmpRedistribution |= ((participants << shiftRedistributionParticipants) & maskRedistributionParticipants);
redistributions[redistributions.length-1] = bytes32(tmpRedistribution);
return true;
}
// Save the given demurrage period as the currently participation period for the given address
function registerAccountPeriod(address _account, uint256 _period) private returns (bool) {
account[_account] &= bytes32(~maskAccountPeriod);
account[_account] |= bytes32((_period << shiftAccountPeriod) & maskAccountPeriod);
incrementRedistributionParticipants();
return true;
}
/// Implements ERC20
function balanceOf(address _account) public view returns (uint256) {
uint256 baseBalance;
@@ -199,8 +153,10 @@ contract DemurrageTokenSingleCap {
baseBalance = baseBalanceOf(_account);
//periodCount = actualPeriod() - demurragePeriod;
periodCount = getMinutesDelta(demurrageTimestamp);
//currentDemurragedAmount = uint128(decayBy(demurrageAmount, periodCount));
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
@@ -208,7 +164,7 @@ contract DemurrageTokenSingleCap {
/// Balance unmodified by demurrage
function baseBalanceOf(address _account) public view returns (uint256) {
return uint256(account[_account]) & maskAccountValue;
return account[_account];
}
/// Increases base balance for a single account
@@ -224,11 +180,7 @@ contract DemurrageTokenSingleCap {
}
oldBalance = baseBalanceOf(_account);
newBalance = oldBalance + _delta;
require(uint160(newBalance) > uint160(oldBalance), 'ERR_WOULDWRAP'); // revert if increase would result in a wrapped value
workAccount &= (~maskAccountValue);
workAccount |= (newBalance & maskAccountValue);
account[_account] = bytes32(workAccount);
account[_account] = oldBalance + _delta;
return true;
}
@@ -246,54 +198,10 @@ contract DemurrageTokenSingleCap {
oldBalance = baseBalanceOf(_account);
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
newBalance = oldBalance - _delta;
workAccount &= (~maskAccountValue);
workAccount |= (newBalance & maskAccountValue);
account[_account] = bytes32(workAccount);
account[_account] = oldBalance - _delta;
return true;
}
// /// Balance unmodified by demurrage
// function baseBalanceOf(address _account) public view returns (uint256) {
// return account[_account];
// }
//
// /// Increases base balance for a single account
// function increaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
// uint256 oldBalance;
// uint256 newBalance;
// uint256 workAccount;
//
// workAccount = uint256(account[_account]);
//
// if (_delta == 0) {
// return false;
// }
//
// oldBalance = baseBalanceOf(_account);
// account[_account] = oldBalance + _delta;
// return true;
// }
//
// /// Decreases base balance for a single account
// function decreaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
// uint256 oldBalance;
// uint256 newBalance;
// uint256 workAccount;
//
// workAccount = uint256(account[_account]);
//
// if (_delta == 0) {
// return false;
// }
//
// oldBalance = baseBalanceOf(_account);
// require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
// account[_account] = oldBalance - _delta;
// return true;
// }
// Creates new tokens out of thin air, and allocates them to the given address
// Triggers tax
function mintTo(address _beneficiary, uint256 _amount) external returns (bool) {
@@ -303,7 +211,7 @@ contract DemurrageTokenSingleCap {
changePeriod();
baseAmount = toBaseAmount(_amount);
supply += _amount;
totalSupply += _amount;
increaseBaseBalance(_beneficiary, baseAmount);
emit Mint(msg.sender, _beneficiary, _amount);
saveRedistributionSupply();
@@ -336,11 +244,6 @@ contract DemurrageTokenSingleCap {
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionParticipants(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionParticipants) >> shiftRedistributionParticipants;
}
// Client accessor to the redistributions array length
function redistributionCount() public view returns (uint256) {
return redistributions.length;
@@ -351,7 +254,8 @@ contract DemurrageTokenSingleCap {
uint256 currentRedistribution;
uint256 grownSupply;
grownSupply = totalSupply();
//grownSupply = growBy(totalSupply, 1);
grownSupply = totalSupply;
currentRedistribution = uint256(redistributions[redistributions.length-1]);
currentRedistribution &= (~maskRedistributionValue);
currentRedistribution |= (grownSupply << shiftRedistributionValue);
@@ -365,12 +269,12 @@ contract DemurrageTokenSingleCap {
return uint128((block.timestamp - periodStart) / periodDuration + 1);
}
// Retrieve next redistribution if the period threshold has been crossed
// Add an entered demurrage period to the redistribution array
function checkPeriod() private view returns (bytes32) {
bytes32 lastRedistribution;
uint256 currentPeriod;
lastRedistribution = redistributions[lastPeriod];
lastRedistribution = redistributions[redistributions.length-1];
currentPeriod = this.actualPeriod();
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
return bytes32(0x00);
@@ -381,7 +285,8 @@ contract DemurrageTokenSingleCap {
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
uint256 difference;
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000));
//difference = _supply * (resolutionFactor - _demurrageAmount); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000)); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
return difference / resolutionFactor;
}
@@ -397,14 +302,9 @@ contract DemurrageTokenSingleCap {
// Returns the amount sent to the sink address
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
uint256 unit;
uint256 baseUnit;
unit = getDistributionFromRedistribution(_redistribution);
baseUnit = toBaseAmount(unit) - totalSink;
increaseBaseBalance(sinkAddress, baseUnit);
lastPeriod += 1;
totalSink += baseUnit;
registerAccountPeriod(sinkAddress, lastPeriod);
increaseBaseBalance(sinkAddress, toBaseAmount(unit));
return unit;
}
@@ -419,9 +319,13 @@ contract DemurrageTokenSingleCap {
}
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
//uint128 epochPeriodCount;
uint256 periodCount;
uint256 lastDemurrageAmount;
//epochPeriodCount = actualPeriod();
//periodCount = epochPeriodCount - demurragePeriod;
periodCount = getMinutesDelta(demurrageTimestamp);
if (periodCount == 0) {
return false;
@@ -452,15 +356,14 @@ contract DemurrageTokenSingleCap {
}
// Recalculate the demurrage modifier for the new period
// Note that the supply for the consecutive period will be taken at the time of code execution, and thus not necessarily at the time when the redistribution period threshold was crossed.
function changePeriod() public returns (bool) {
bytes32 currentRedistribution;
bytes32 nextRedistribution;
uint256 currentPeriod;
uint256 lastDemurrageAmount;
bytes32 lastRedistribution;
uint256 currentDemurrageAmount;
uint256 nextRedistributionDemurrage;
uint256 demurrageCounts;
uint256 periodTimestamp;
uint256 nextPeriod;
applyDemurrage();
@@ -469,15 +372,20 @@ contract DemurrageTokenSingleCap {
return false;
}
// calculate the decay from previous redistributino
lastRedistribution = redistributions[lastPeriod];
currentPeriod = toRedistributionPeriod(currentRedistribution);
nextPeriod = currentPeriod + 1;
lastDemurrageAmount = toRedistributionDemurrageModifier(lastRedistribution);
demurrageCounts = periodDuration / 60;
nextRedistributionDemurrage = decayBy(lastDemurrageAmount, demurrageCounts);
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply(), nextPeriod);
periodTimestamp = getPeriodTimeDelta(currentPeriod);
currentDemurrageAmount = demurrageAmount;
demurrageCounts = demurrageCycles(periodTimestamp);
if (demurrageCounts > 0) {
nextRedistributionDemurrage = growBy(currentDemurrageAmount, demurrageCounts);
} else {
nextRedistributionDemurrage = currentDemurrageAmount;
}
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, nextPeriod);
redistributions.push(nextRedistribution);
applyDefaultRedistribution(nextRedistribution);
@@ -515,6 +423,7 @@ contract DemurrageTokenSingleCap {
// Inflates the given amount according to the current demurrage modifier
function toBaseAmount(uint256 _value) public view returns (uint256) {
//return (_value * resolutionFactor) / demurrageAmount;
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
}
@@ -522,42 +431,11 @@ contract DemurrageTokenSingleCap {
function approve(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
if (allowance[msg.sender][_spender] > 0) {
require(_value == 0, 'ZERO_FIRST');
}
changePeriod();
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] = baseValue;
emit Approval(msg.sender, _spender, _value);
return true;
}
// Reduce allowance by amount
function decreaseAllowance(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
baseValue = toBaseAmount(_value);
require(allowance[msg.sender][_spender] >= baseValue);
changePeriod();
allowance[msg.sender][_spender] -= baseValue;
emit Approval(msg.sender, _spender, allowance[msg.sender][_spender]);
return true;
}
// Increase allowance by amount
function increaseAllowance(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
changePeriod();
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] += baseValue;
emit Approval(msg.sender, _spender, allowance[msg.sender][_spender]);
emit Approval(msg.sender, _spender, _value);
return true;
}
@@ -584,9 +462,7 @@ contract DemurrageTokenSingleCap {
baseValue = toBaseAmount(_value);
require(allowance[_from][msg.sender] >= baseValue);
allowance[_from][msg.sender] -= baseValue;
result = transferBase(_from, _to, baseValue);
emit Transfer(_from, _to, _value);
return result;
}
@@ -597,11 +473,8 @@ contract DemurrageTokenSingleCap {
decreaseBaseBalance(_from, _value);
increaseBaseBalance(_to, _value);
period = actualPeriod();
if (accountPeriod(_from) != period && _from != _to) {
registerAccountPeriod(_from, period);
}
//period = actualPeriod();
return true;
}
@@ -622,29 +495,6 @@ contract DemurrageTokenSingleCap {
emit OwnershipTransferred(oldOwner, owner);
}
// Explicitly and irretrievably burn tokens
// Only token minters can burn tokens
function burn(uint256 _value) public {
require(minter[msg.sender]);
//require(_value <= account[msg.sender]);
uint256 _delta = toBaseAmount(_value);
applyDemurrage();
decreaseBaseBalance(msg.sender, _delta);
burned += _value;
emit Burn(msg.sender, _value);
}
// Implements ERC20
function totalSupply() public view returns (uint256) {
return supply - burned;
}
// Return total number of burned tokens
function totalBurned() public view returns (uint256) {
return burned;
}
// Implements EIP165
function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20

View File

@@ -33,10 +33,6 @@ test: all
python ../python/tests/test_pure.py
install: all
#cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
cp -v DemurrageTokenSingleNocap.{json,bin} ../python/erc20_demurrage_token/data/
install-broken: all
cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
.PHONY: test install