Compare commits
17 Commits
dev-0.2.0
...
lash/simpl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b6a88c889
|
||
|
|
00f4ddba68
|
||
|
|
9d7b87be9c
|
||
|
|
697d2bf227
|
||
|
|
e49493def1
|
||
|
|
4f1bd36c12
|
||
|
|
166302ef49
|
||
|
|
2ec72bbc55
|
||
|
|
97415dbed2
|
||
|
|
3b87961d13
|
||
|
|
a56f1c4869
|
||
|
|
ef294eaec2
|
||
|
|
2f4f8bb7b9
|
||
|
|
140dde7bc3
|
||
|
|
90fb95208f
|
||
| 99a9915d11 | |||
|
|
20e3a783fd |
5
CAVEAT
Normal file
5
CAVEAT
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
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.
|
||||||
8
ROADMAP
Normal file
8
ROADMAP
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
- 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.
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
- 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
|
||||||
|
* Dependency upgrades
|
||||||
- 0.0.11
|
- 0.0.11
|
||||||
* Apply demurrage cli tool
|
* Apply demurrage cli tool
|
||||||
- 0.0.10
|
- 0.0.10
|
||||||
|
|||||||
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
@@ -9,8 +9,7 @@ from chainlib.eth.constant import ZERO_ADDRESS
|
|||||||
# local imports
|
# local imports
|
||||||
from .token import DemurrageToken
|
from .token import DemurrageToken
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logg = logging.getLogger(__name__)
|
||||||
logg = logging.getLogger()
|
|
||||||
|
|
||||||
|
|
||||||
class DemurrageCalculator:
|
class DemurrageCalculator:
|
||||||
|
|||||||
@@ -11,24 +11,34 @@ import os
|
|||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
import datetime
|
||||||
|
import math
|
||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
import confini
|
import confini
|
||||||
from funga.eth.signer import EIP155Signer
|
import chainlib.eth.cli
|
||||||
from funga.eth.keystore.dict import DictKeystore
|
from chainlib.eth.block import (
|
||||||
from chainlib.chain import ChainSpec
|
block_latest,
|
||||||
from chainlib.eth.nonce import (
|
block_by_number,
|
||||||
RPCNonceOracle,
|
Block,
|
||||||
OverrideNonceOracle,
|
|
||||||
)
|
|
||||||
from chainlib.eth.gas import (
|
|
||||||
RPCGasOracle,
|
|
||||||
OverrideGasOracle,
|
|
||||||
)
|
)
|
||||||
from chainlib.eth.connection import EthHTTPConnection
|
from chainlib.eth.connection import EthHTTPConnection
|
||||||
from chainlib.eth.tx import receipt
|
from chainlib.eth.tx import receipt
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
|
from hexathon import to_int as hex_to_int
|
||||||
import chainlib.eth.cli
|
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
|
# local imports
|
||||||
import erc20_demurrage_token
|
import erc20_demurrage_token
|
||||||
@@ -37,53 +47,105 @@ from erc20_demurrage_token import (
|
|||||||
DemurrageTokenSettings,
|
DemurrageTokenSettings,
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
script_dir = os.path.dirname(__file__)
|
|
||||||
data_dir = os.path.join(script_dir, '..', 'data')
|
|
||||||
|
|
||||||
config_dir = os.path.join(data_dir, 'config')
|
def process_config_local(config, arg, args, flags):
|
||||||
|
config.add(args.steps, '_STEPS', False)
|
||||||
|
return config
|
||||||
|
|
||||||
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC
|
|
||||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
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)
|
||||||
|
argparser.add_argument('--steps', type=int, default=0, help='Max demurrage steps to apply per round')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_fee_limit=DemurrageToken.gas(), base_config_dir=config_dir)
|
|
||||||
|
|
||||||
wallet = chainlib.eth.cli.Wallet()
|
logg = process_log(args, logg)
|
||||||
wallet.from_config(config)
|
|
||||||
|
|
||||||
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
config = Config()
|
||||||
conn = rpc.connect_by_config(config)
|
config = process_config(config, arg, args, flags)
|
||||||
|
config = process_config_local(config, arg, args, flags)
|
||||||
|
logg.debug('config loaded:\n{}'.format(config))
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
settings = ChainSettings()
|
||||||
|
settings = process_settings(settings, config)
|
||||||
|
logg.debug('settings loaded:\n{}'.format(settings))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
signer = rpc.get_signer()
|
chain_spec = settings.get('CHAIN_SPEC')
|
||||||
signer_address = rpc.get_sender_address()
|
conn = settings.get('CONN')
|
||||||
|
o = block_latest()
|
||||||
|
r = conn.do(o)
|
||||||
|
|
||||||
|
block_start_number = None
|
||||||
|
try:
|
||||||
|
block_start_number = hex_to_int(r)
|
||||||
|
except TypeError:
|
||||||
|
block_start_number = int(r)
|
||||||
|
|
||||||
gas_oracle = rpc.get_gas_oracle()
|
o = block_by_number(block_start_number)
|
||||||
nonce_oracle = rpc.get_nonce_oracle()
|
r = conn.do(o)
|
||||||
|
|
||||||
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
block_start = Block(r)
|
||||||
(tx_hash_hex, o) = c.apply_demurrage(config.get('_EXEC_ADDRESS'), signer_address)
|
block_start_timestamp = block_start.timestamp
|
||||||
if config.get('_RPC_SEND'):
|
block_start_datetime = datetime.datetime.fromtimestamp(block_start_timestamp)
|
||||||
conn.do(o)
|
|
||||||
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')
|
|
||||||
sys.exit(1)
|
|
||||||
# TODO: pass through translator for keys (evm tester uses underscore instead of camelcase)
|
|
||||||
address = r['contractAddress']
|
|
||||||
|
|
||||||
print(address)
|
gas_oracle = settings.get('FEE_ORACLE')
|
||||||
else:
|
c = DemurrageToken(chain_spec, gas_oracle=gas_oracle)
|
||||||
|
o = c.demurrage_timestamp(settings.get('EXEC'))
|
||||||
|
r = conn.do(o)
|
||||||
|
|
||||||
|
demurrage_timestamp = None
|
||||||
|
try:
|
||||||
|
demurrage_timestamp = hex_to_int(r)
|
||||||
|
except TypeError:
|
||||||
|
demurrage_timestamp = int(r)
|
||||||
|
demurrage_datetime = datetime.datetime.fromtimestamp(demurrage_timestamp)
|
||||||
|
|
||||||
|
total_seconds = block_start_timestamp - demurrage_timestamp
|
||||||
|
total_steps = total_seconds / 60
|
||||||
|
|
||||||
|
if total_steps < 1.0:
|
||||||
|
logg.error('only {} seconds since last demurrage application, skipping'.format(total_seconds))
|
||||||
|
return
|
||||||
|
|
||||||
|
logg.debug('block start is at {} demurrage is at {} -> {} minutes'.format(
|
||||||
|
block_start_datetime,
|
||||||
|
demurrage_datetime,
|
||||||
|
total_steps,
|
||||||
|
))
|
||||||
|
|
||||||
|
rounds = 1
|
||||||
|
if config.get('_STEPS') > 0:
|
||||||
|
rounds = math.ceil(total_steps / config.get('_STEPS'))
|
||||||
|
|
||||||
|
logg.info('will perform {} rounds of {} steps'.format(rounds, config.get('_STEPS')))
|
||||||
|
|
||||||
|
last_tx_hash = None
|
||||||
|
for i in range(rounds):
|
||||||
|
signer = settings.get('SIGNER')
|
||||||
|
signer_address = settings.get('SENDER_ADDRESS')
|
||||||
|
|
||||||
|
nonce_oracle = settings.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'):
|
||||||
print(tx_hash_hex)
|
print(tx_hash_hex)
|
||||||
|
conn.do(o)
|
||||||
|
if config.true('_WAIT_ALL') or (i == rounds - 1 and config.true('_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')
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print(o)
|
||||||
|
|
||||||
else:
|
|
||||||
print(o)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -14,21 +14,22 @@ import logging
|
|||||||
|
|
||||||
# external imports
|
# external imports
|
||||||
import confini
|
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.connection import EthHTTPConnection
|
||||||
from chainlib.eth.tx import receipt
|
from chainlib.eth.tx import receipt
|
||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
import chainlib.eth.cli
|
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
|
# local imports
|
||||||
import erc20_demurrage_token
|
import erc20_demurrage_token
|
||||||
@@ -37,84 +38,75 @@ from erc20_demurrage_token import (
|
|||||||
DemurrageTokenSettings,
|
DemurrageTokenSettings,
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
script_dir = os.path.dirname(__file__)
|
|
||||||
data_dir = os.path.join(script_dir, '..', 'data')
|
|
||||||
|
|
||||||
config_dir = os.path.join(data_dir, 'config')
|
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
|
||||||
|
|
||||||
arg_flags = chainlib.eth.cli.argflag_std_write
|
|
||||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
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)
|
||||||
argparser.add_argument('--name', dest='token_name', type=str, help='Token name')
|
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('--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('--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('--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('--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('--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('--multi', action='store_true', help='automatic redistribution')
|
||||||
argparser.add_argument('--demurrage-level', dest='demurrage_level', type=int, help='demurrage level, ppm per minute')
|
argparser.add_argument('--demurrage-level', dest='demurrage_level', type=int, help='demurrage level, ppm per minute')
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
arg_flags = chainlib.eth.cli.argflag_std_write
|
logg = process_log(args, logg)
|
||||||
|
|
||||||
extra_args = {
|
config = Config()
|
||||||
'redistribution_period': 'TOKEN_REDISTRIBUTION_PERIOD',
|
config = process_config(config, arg, args, flags)
|
||||||
'demurrage_level': 'TOKEN_DEMURRAGE_LEVEL',
|
config = process_config_local(config, arg, args, flags)
|
||||||
'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))
|
logg.debug('config loaded:\n{}'.format(config))
|
||||||
|
|
||||||
wallet = chainlib.eth.cli.Wallet()
|
settings = ChainSettings()
|
||||||
wallet.from_config(config)
|
settings = process_settings(settings, config)
|
||||||
|
logg.debug('settings loaded:\n{}'.format(settings))
|
||||||
|
|
||||||
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():
|
def main():
|
||||||
signer = rpc.get_signer()
|
chain_spec = settings.get('CHAIN_SPEC')
|
||||||
signer_address = rpc.get_sender_address()
|
conn = settings.get('CONN')
|
||||||
|
signer = settings.get('SIGNER')
|
||||||
|
signer_address = settings.get('SENDER_ADDRESS')
|
||||||
|
|
||||||
gas_oracle = rpc.get_gas_oracle()
|
gas_oracle = settings.get('FEE_ORACLE')
|
||||||
nonce_oracle = rpc.get_nonce_oracle()
|
nonce_oracle = settings.get('NONCE_ORACLE')
|
||||||
|
|
||||||
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
settings = DemurrageTokenSettings()
|
token_settings = DemurrageTokenSettings()
|
||||||
settings.name = config.get('TOKEN_NAME')
|
token_settings.name = config.get('TOKEN_NAME')
|
||||||
settings.symbol = config.get('TOKEN_SYMBOL')
|
token_settings.symbol = config.get('TOKEN_SYMBOL')
|
||||||
settings.decimals = int(config.get('TOKEN_DECIMALS'))
|
token_settings.decimals = int(config.get('TOKEN_DECIMALS'))
|
||||||
settings.demurrage_level = int(config.get('TOKEN_DEMURRAGE_LEVEL'))
|
token_settings.demurrage_level = int(config.get('TOKEN_DEMURRAGE_LEVEL'))
|
||||||
settings.period_minutes = int(config.get('TOKEN_REDISTRIBUTION_PERIOD'))
|
token_settings.period_minutes = int(config.get('TOKEN_REDISTRIBUTION_PERIOD'))
|
||||||
settings.sink_address = config.get('TOKEN_SINK_ADDRESS')
|
token_settings.sink_address = config.get('TOKEN_SINK_ADDRESS')
|
||||||
|
|
||||||
(tx_hash_hex, o) = c.constructor(
|
(tx_hash_hex, o) = c.constructor(
|
||||||
signer_address,
|
signer_address,
|
||||||
settings,
|
token_settings,
|
||||||
redistribute=config.true('_MULTI'),
|
redistribute=config.true('_MULTI'),
|
||||||
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
|
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
|
||||||
)
|
)
|
||||||
if config.get('_RPC_SEND'):
|
if settings.get('RPC_SEND'):
|
||||||
conn.do(o)
|
conn.do(o)
|
||||||
if config.get('_WAIT'):
|
if config.true('_WAIT'):
|
||||||
r = conn.wait(tx_hash_hex)
|
r = conn.wait(tx_hash_hex)
|
||||||
if r['status'] == 0:
|
if r['status'] == 0:
|
||||||
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')
|
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ class DemurrageTokenSettings:
|
|||||||
self.sink_address = None
|
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):
|
class DemurrageToken(ERC20):
|
||||||
|
|
||||||
__abi = {}
|
__abi = {}
|
||||||
@@ -108,6 +117,34 @@ class DemurrageToken(ERC20):
|
|||||||
return DemurrageToken.__bytecode[name]
|
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):
|
def add_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||||
enc = ABIContractEncoder()
|
enc = ABIContractEncoder()
|
||||||
enc.method('addMinter')
|
enc.method('addMinter')
|
||||||
@@ -146,6 +183,33 @@ class DemurrageToken(ERC20):
|
|||||||
return tx
|
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):
|
def to_base_amount(self, contract_address, value, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
o = j.template()
|
||||||
@@ -246,8 +310,11 @@ class DemurrageToken(ERC20):
|
|||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
enc = ABIContractEncoder()
|
enc = ABIContractEncoder()
|
||||||
enc.method('toRedistributionPeriod')
|
enc.method('toRedistributionPeriod')
|
||||||
enc.typ(ABIContractType.BYTES32)
|
v = strip_0x(redistribution)
|
||||||
enc.bytes32(redistribution)
|
enc.typ_literal('(uint32,uint72,uint104)')
|
||||||
|
enc.bytes32(v[:64])
|
||||||
|
enc.bytes32(v[64:128])
|
||||||
|
enc.bytes32(v[128:192])
|
||||||
data = add_0x(enc.get())
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
@@ -257,22 +324,26 @@ class DemurrageToken(ERC20):
|
|||||||
return o
|
return o
|
||||||
|
|
||||||
|
|
||||||
def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
# def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||||
j = JSONRPCRequest(id_generator)
|
# j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
# o = j.template()
|
||||||
o['method'] = 'eth_call'
|
# o['method'] = 'eth_call'
|
||||||
enc = ABIContractEncoder()
|
# enc = ABIContractEncoder()
|
||||||
enc.method('toRedistributionParticipants')
|
# enc.method('toRedistributionParticipants')
|
||||||
enc.typ(ABIContractType.BYTES32)
|
# v = strip_0x(redistribution)
|
||||||
enc.bytes32(redistribution)
|
# enc.typ_literal('(uint32,uint72,uint104)')
|
||||||
data = add_0x(enc.get())
|
# #enc.typ(ABIContractType.BYTES32)
|
||||||
tx = self.template(sender_address, contract_address)
|
# enc.bytes32(v[:64])
|
||||||
tx = self.set_code(tx, data)
|
# enc.bytes32(v[64:128])
|
||||||
o['params'].append(self.normalize(tx))
|
# enc.bytes32(v[128:192])
|
||||||
o['params'].append('latest')
|
# data = add_0x(enc.get())
|
||||||
o = j.finalize(o)
|
# tx = self.template(sender_address, contract_address)
|
||||||
return o
|
# tx = self.set_code(tx, data)
|
||||||
|
# o['params'].append(self.normalize(tx))
|
||||||
|
# o['params'].append('latest')
|
||||||
|
# o = j.finalize(o)
|
||||||
|
# return o
|
||||||
|
#
|
||||||
|
|
||||||
def to_redistribution_supply(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
def to_redistribution_supply(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
@@ -280,8 +351,11 @@ class DemurrageToken(ERC20):
|
|||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
enc = ABIContractEncoder()
|
enc = ABIContractEncoder()
|
||||||
enc.method('toRedistributionSupply')
|
enc.method('toRedistributionSupply')
|
||||||
enc.typ(ABIContractType.BYTES32)
|
v = strip_0x(redistribution)
|
||||||
enc.bytes32(redistribution)
|
enc.typ_literal('(uint32,uint72,uint104)')
|
||||||
|
enc.bytes32(v[:64])
|
||||||
|
enc.bytes32(v[64:128])
|
||||||
|
enc.bytes32(v[128:192])
|
||||||
data = add_0x(enc.get())
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
@@ -297,8 +371,11 @@ class DemurrageToken(ERC20):
|
|||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
enc = ABIContractEncoder()
|
enc = ABIContractEncoder()
|
||||||
enc.method('toRedistributionDemurrageModifier')
|
enc.method('toRedistributionDemurrageModifier')
|
||||||
enc.typ(ABIContractType.BYTES32)
|
v = strip_0x(redistribution)
|
||||||
enc.bytes32(redistribution)
|
enc.typ_literal('(uint32,uint72,uint104)')
|
||||||
|
enc.bytes32(v[:64])
|
||||||
|
enc.bytes32(v[64:128])
|
||||||
|
enc.bytes32(v[128:192])
|
||||||
data = add_0x(enc.get())
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
@@ -380,28 +457,32 @@ class DemurrageToken(ERC20):
|
|||||||
return self.call_noarg('demurrageAmount', contract_address, sender_address=sender_address)
|
return self.call_noarg('demurrageAmount', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
|
|
||||||
|
def demurrage_timestamp(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
|
return self.call_noarg('demurrageTimestamp', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
|
|
||||||
def supply_cap(self, contract_address, sender_address=ZERO_ADDRESS):
|
def supply_cap(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
return self.call_noarg('supplyCap', contract_address, sender_address=sender_address)
|
return self.call_noarg('supplyCap', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
|
|
||||||
def grow_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
# def grow_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||||
j = JSONRPCRequest(id_generator)
|
# j = JSONRPCRequest(id_generator)
|
||||||
o = j.template()
|
# o = j.template()
|
||||||
o['method'] = 'eth_call'
|
# o['method'] = 'eth_call'
|
||||||
enc = ABIContractEncoder()
|
# enc = ABIContractEncoder()
|
||||||
enc.method('growBy')
|
# enc.method('growBy')
|
||||||
enc.typ(ABIContractType.UINT256)
|
# enc.typ(ABIContractType.UINT256)
|
||||||
enc.typ(ABIContractType.UINT256)
|
# enc.typ(ABIContractType.UINT256)
|
||||||
enc.uint256(value)
|
# enc.uint256(value)
|
||||||
enc.uint256(period)
|
# enc.uint256(period)
|
||||||
data = add_0x(enc.get())
|
# data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
# tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
# tx = self.set_code(tx, data)
|
||||||
o['params'].append(self.normalize(tx))
|
# o['params'].append(self.normalize(tx))
|
||||||
o['params'].append('latest')
|
# o['params'].append('latest')
|
||||||
o = j.finalize(o)
|
# o = j.finalize(o)
|
||||||
return o
|
# return o
|
||||||
|
#
|
||||||
|
|
||||||
def decay_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
def decay_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||||
j = JSONRPCRequest(id_generator)
|
j = JSONRPCRequest(id_generator)
|
||||||
@@ -447,8 +528,11 @@ class DemurrageToken(ERC20):
|
|||||||
o['method'] = 'eth_call'
|
o['method'] = 'eth_call'
|
||||||
enc = ABIContractEncoder()
|
enc = ABIContractEncoder()
|
||||||
enc.method('getDistributionFromRedistribution')
|
enc.method('getDistributionFromRedistribution')
|
||||||
enc.typ(ABIContractType.BYTES32)
|
v = strip_0x(redistribution)
|
||||||
enc.bytes32(redistribution)
|
enc.typ_literal('(uint32,uint72,uint104)')
|
||||||
|
enc.bytes32(v[:64])
|
||||||
|
enc.bytes32(v[64:128])
|
||||||
|
enc.bytes32(v[128:192])
|
||||||
data = add_0x(enc.get())
|
data = add_0x(enc.get())
|
||||||
tx = self.template(sender_address, contract_address)
|
tx = self.template(sender_address, contract_address)
|
||||||
tx = self.set_code(tx, data)
|
tx = self.set_code(tx, data)
|
||||||
@@ -513,6 +597,7 @@ class DemurrageToken(ERC20):
|
|||||||
def parse_supply_cap(self, v):
|
def parse_supply_cap(self, v):
|
||||||
return abi_decode_single(ABIContractType.UINT256, v)
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_grow_by(self, v):
|
def parse_grow_by(self, v):
|
||||||
return abi_decode_single(ABIContractType.UINT256, v)
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
@@ -536,3 +621,8 @@ class DemurrageToken(ERC20):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def parse_resolution_factor(self, v):
|
def parse_resolution_factor(self, v):
|
||||||
return abi_decode_single(ABIContractType.UINT256, v)
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_total_burned(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from erc20_demurrage_token import (
|
|||||||
DemurrageToken,
|
DemurrageToken,
|
||||||
)
|
)
|
||||||
|
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger(__name__)
|
||||||
|
|
||||||
#BLOCKTIME = 5 # seconds
|
#BLOCKTIME = 5 # seconds
|
||||||
TAX_LEVEL = int(10000 * 2) # 2%
|
TAX_LEVEL = int(10000 * 2) # 2%
|
||||||
@@ -32,18 +32,19 @@ PERIOD = 10
|
|||||||
|
|
||||||
class TestTokenDeploy:
|
class TestTokenDeploy:
|
||||||
|
|
||||||
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, supply=10**12):
|
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.tax_level = tax_level
|
||||||
self.period_seconds = PERIOD * 60
|
self.period_seconds = period * 60
|
||||||
|
|
||||||
self.settings = DemurrageTokenSettings()
|
self.settings = DemurrageTokenSettings()
|
||||||
self.settings.name = token_name
|
self.settings.name = token_name
|
||||||
self.settings.symbol = token_symbol
|
self.settings.symbol = token_symbol
|
||||||
self.settings.decimals = 6
|
self.settings.decimals = 6
|
||||||
self.settings.demurrage_level = TAX_LEVEL * (10 ** 32)
|
self.settings.demurrage_level = tax_level * (10 ** 32)
|
||||||
self.settings.period_minutes = PERIOD
|
self.settings.period_minutes = period
|
||||||
self.settings.sink_address = sink_address
|
self.settings.sink_address = sink_address
|
||||||
self.sink_address = self.settings.sink_address
|
self.sink_address = self.settings.sink_address
|
||||||
|
logg.debug('using demurrage token settings: {}'.format(self.settings))
|
||||||
|
|
||||||
o = block_latest()
|
o = block_latest()
|
||||||
self.start_block = rpc.do(o)
|
self.start_block = rpc.do(o)
|
||||||
@@ -94,14 +95,12 @@ class TestDemurrage(EthTesterCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDemurrage, self).setUp()
|
super(TestDemurrage, self).setUp()
|
||||||
# token_deploy = TestTokenDeploy()
|
period = PERIOD
|
||||||
# self.settings = token_deploy.settings
|
try:
|
||||||
# self.sink_address = token_deploy.sink_address
|
period = getattr(self, 'period')
|
||||||
# self.start_block = token_deploy.start_block
|
except AttributeError as e:
|
||||||
# self.start_time = token_deploy.start_time
|
pass
|
||||||
# self.default_supply = self.default_supply
|
self.deployer = TestTokenDeploy(self.rpc, period=period)
|
||||||
# self.default_supply_cap = self.default_supply_cap
|
|
||||||
self.deployer = TestTokenDeploy(self.rpc)
|
|
||||||
self.default_supply = self.deployer.default_supply
|
self.default_supply = self.deployer.default_supply
|
||||||
self.default_supply_cap = self.deployer.default_supply_cap
|
self.default_supply_cap = self.deployer.default_supply_cap
|
||||||
self.start_block = None
|
self.start_block = None
|
||||||
@@ -127,6 +126,13 @@ class TestDemurrage(EthTesterCase):
|
|||||||
logg.debug('asserted within lower {} <= {} <= {}'.format(lower_target, v, target))
|
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):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -203,10 +209,11 @@ class TestDemurrageCap(TestDemurrage):
|
|||||||
class TestDemurrageUnit(TestDemurrage):
|
class TestDemurrageUnit(TestDemurrage):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDemurrage, self).setUp()
|
self.period = 1
|
||||||
|
self.period_seconds = self.period * 60
|
||||||
|
self.tax_level = TAX_LEVEL
|
||||||
|
|
||||||
self.tax_level = 50
|
super(TestDemurrageUnit, self).setUp()
|
||||||
self.period_seconds = 60
|
|
||||||
|
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
self.settings = DemurrageTokenSettings()
|
self.settings = DemurrageTokenSettings()
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
chainlib-eth~=0.0.27
|
chainlib-eth~=0.4.11
|
||||||
eth-erc20~=0.1.10
|
eth-erc20~=0.5.1
|
||||||
funga-eth~=0.5.6
|
funga-eth~=0.6.0
|
||||||
|
|||||||
@@ -6,21 +6,30 @@ set -e
|
|||||||
export PYTHONPATH=.
|
export PYTHONPATH=.
|
||||||
|
|
||||||
#modes=(MultiNocap MultiCap SingleCap SingleNocap)
|
#modes=(MultiNocap MultiCap SingleCap SingleNocap)
|
||||||
modes=(SingleCap SingleNocap) # other contracts need to be updted
|
#modes=(SingleCap SingleNocap) # other contracts need to be updted
|
||||||
|
modes=(SingleNocap) # other contracts need to be updted
|
||||||
for m in ${modes[@]}; do
|
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_basic.py
|
||||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_growth.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_amounts.py
|
||||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_single.py
|
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_single.py
|
||||||
|
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_burn.py
|
||||||
done
|
done
|
||||||
|
|
||||||
modes=(SingleCap) # other contracts need to be updted
|
#modes=(SingleCap) # other contracts need to be updted
|
||||||
|
modes=()
|
||||||
for m in ${modes[@]}; do
|
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_period.py
|
||||||
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
|
|
||||||
done
|
done
|
||||||
|
|
||||||
modes=(MultiCap SingleCap)
|
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=()
|
||||||
for m in ${modes[@]}; do
|
for m in ${modes[@]}; do
|
||||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_cap.py
|
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_cap.py
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = erc20-demurrage-token
|
name = erc20-demurrage-token
|
||||||
version = 0.0.11
|
version = 0.2.0
|
||||||
description = ERC20 token with redistributed continual demurrage
|
description = ERC20 token with redistributed continual demurrage
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
@@ -25,12 +25,13 @@ licence_files =
|
|||||||
|
|
||||||
[options]
|
[options]
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
python_requires = >= 3.6
|
python_requires = >= 3.7
|
||||||
packages =
|
packages =
|
||||||
erc20_demurrage_token
|
erc20_demurrage_token
|
||||||
erc20_demurrage_token.runnable
|
erc20_demurrage_token.runnable
|
||||||
erc20_demurrage_token.data
|
erc20_demurrage_token.data
|
||||||
erc20_demurrage_token.sim
|
erc20_demurrage_token.sim
|
||||||
|
erc20_demurrage_token.unittest
|
||||||
|
|
||||||
[options.package_data]
|
[options.package_data]
|
||||||
* =
|
* =
|
||||||
|
|||||||
@@ -276,7 +276,52 @@ class TestBasic(TestDemurrageDefault):
|
|||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
self.assertEqual(r['status'], 1)
|
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):
|
def test_transfer_from(self):
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
@@ -315,6 +360,12 @@ class TestBasic(TestDemurrageDefault):
|
|||||||
balance = c.parse_balance_of(r)
|
balance = c.parse_balance_of(r)
|
||||||
self.assertEqual(balance, 500)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
320
python/tests/test_burn.py
Normal file
320
python/tests/test_burn.py
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
# 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()
|
||||||
133
python/tests/test_demurrage.py
Normal file
133
python/tests/test_demurrage.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# 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()
|
||||||
@@ -28,23 +28,23 @@ testdir = os.path.dirname(__file__)
|
|||||||
|
|
||||||
class TestGrowth(TestDemurrageDefault):
|
class TestGrowth(TestDemurrageDefault):
|
||||||
|
|
||||||
def test_grow_by(self):
|
# def test_grow_by(self):
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||||
|
#
|
||||||
growth_factor = (1000000 + self.tax_level) / 1000000
|
# growth_factor = (1000000 + self.tax_level) / 1000000
|
||||||
v = 1000000000
|
# v = 1000000000
|
||||||
o = c.grow_by(self.address, v, 1, sender_address=self.accounts[0])
|
# o = c.grow_by(self.address, v, 1, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
# r = self.rpc.do(o)
|
||||||
g = c.parse_grow_by(r)
|
# g = c.parse_grow_by(r)
|
||||||
self.assertEqual(int(v * growth_factor), g)
|
# self.assertEqual(int(v * growth_factor), g)
|
||||||
|
#
|
||||||
period = 10
|
# period = 10
|
||||||
growth_factor = (1 + (self.tax_level) / 1000000) ** period
|
# growth_factor = (1 + (self.tax_level) / 1000000) ** period
|
||||||
o = c.grow_by(self.address, v, period, sender_address=self.accounts[0])
|
# o = c.grow_by(self.address, v, period, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
# r = self.rpc.do(o)
|
||||||
g = c.parse_grow_by(r)
|
# g = c.parse_grow_by(r)
|
||||||
self.assertEqual(int(v * growth_factor), g)
|
# self.assertEqual(int(v * growth_factor), g)
|
||||||
|
|
||||||
|
|
||||||
def test_decay_by(self):
|
def test_decay_by(self):
|
||||||
@@ -63,7 +63,7 @@ class TestGrowth(TestDemurrageDefault):
|
|||||||
o = c.decay_by(self.address, v, period, sender_address=self.accounts[0])
|
o = c.decay_by(self.address, v, period, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
g = c.parse_decay_by(r)
|
g = c.parse_decay_by(r)
|
||||||
self.assertEqual(int(v * growth_factor), g)
|
self.assertEqual(int(v * growth_factor), g)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ testdir = os.path.dirname(__file__)
|
|||||||
|
|
||||||
class TestRedistribution(TestDemurrageDefault):
|
class TestRedistribution(TestDemurrageDefault):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_whole_is_parts(self):
|
def test_whole_is_parts(self):
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||||
|
|||||||
198
python/tests/test_redistribution_single.py
Normal file
198
python/tests/test_redistribution_single.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# 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()
|
||||||
@@ -31,12 +31,12 @@ testdir = os.path.dirname(__file__)
|
|||||||
|
|
||||||
class TestRedistribution(TestDemurrageUnit):
|
class TestRedistribution(TestDemurrageUnit):
|
||||||
|
|
||||||
|
|
||||||
# TODO: move to "pure" test file when getdistribution is implemented in all contracts
|
# TODO: move to "pure" test file when getdistribution is implemented in all contracts
|
||||||
def test_distribution(self):
|
def test_distribution_direct(self):
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
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)
|
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
|
||||||
supply = self.default_supply
|
supply = self.default_supply
|
||||||
|
|
||||||
@@ -51,21 +51,23 @@ class TestRedistribution(TestDemurrageUnit):
|
|||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
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 / 100000)) * (10**28)
|
||||||
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
|
|
||||||
|
logg.debug('demurrage {}'.format(demurrage))
|
||||||
supply = self.default_supply
|
supply = self.default_supply
|
||||||
|
|
||||||
o = c.to_redistribution(self.address, 0, demurrage, supply, 1, sender_address=self.accounts[0])
|
o = c.to_redistribution(self.address, 0, demurrage, supply, 2, sender_address=self.accounts[0])
|
||||||
redistribution = self.rpc.do(o)
|
redistribution = self.rpc.do(o)
|
||||||
|
|
||||||
o = c.get_distribution_from_redistribution(self.address, redistribution, self.accounts[0])
|
o = c.get_distribution_from_redistribution(self.address, redistribution, self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
distribution = c.parse_get_distribution(r)
|
distribution = c.parse_get_distribution(r)
|
||||||
expected_distribution = self.default_supply * (self.tax_level / 1000000)
|
expected_distribution = (self.default_supply * self.tax_level) / 100000
|
||||||
|
logg.debug('distribution {} supply {}'.format(distribution, self.default_supply))
|
||||||
self.assert_within_lower(distribution, expected_distribution, 1000)
|
self.assert_within_lower(distribution, expected_distribution, 1000)
|
||||||
|
|
||||||
|
|
||||||
def test_single_step(self):
|
def test_single_step_basic(self):
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||||
|
|
||||||
@@ -81,6 +83,12 @@ class TestRedistribution(TestDemurrageUnit):
|
|||||||
|
|
||||||
expected_balance = int(mint_amount - ((self.tax_level / 1000000) * mint_amount))
|
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])
|
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
balance = c.parse_balance(r)
|
balance = c.parse_balance(r)
|
||||||
@@ -169,15 +177,13 @@ class TestRedistribution(TestDemurrageUnit):
|
|||||||
|
|
||||||
o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
||||||
redistribution = self.rpc.do(o)
|
redistribution = self.rpc.do(o)
|
||||||
|
logg.debug('redistribution {}'.format(redistribution))
|
||||||
o = c.to_redistribution_supply(self.address, redistribution, sender_address=self.accounts[0])
|
o = c.to_redistribution_supply(self.address, redistribution, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
supply = c.parse_to_redistribution_item(r)
|
supply = c.parse_to_redistribution_item(r)
|
||||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
demurrage = c.parse_to_redistribution_item(r)
|
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))
|
logg.debug('\nrediistribution {}\ndemurrage {}\nsupply {}'.format(redistribution, demurrage, supply))
|
||||||
|
|
||||||
expected_balance = int(supply * (self.tax_level / 1000000))
|
expected_balance = int(supply * (self.tax_level / 1000000))
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
pragma solidity > 0.6.11;
|
pragma solidity >= 0.8.0;
|
||||||
|
|
||||||
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
contract DemurrageTokenSingleCap {
|
contract DemurrageTokenSingleCap {
|
||||||
|
|
||||||
|
struct redistributionItem {
|
||||||
|
uint32 period;
|
||||||
|
uint72 value;
|
||||||
|
uint104 demurrage;
|
||||||
|
}
|
||||||
|
redistributionItem[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
|
||||||
// Redistribution bit field, with associated shifts and masks
|
// Redistribution bit field, with associated shifts and masks
|
||||||
// (Uses sub-byte boundaries)
|
// (Uses sub-byte boundaries)
|
||||||
bytes32[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
|
// bytes32[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
|
||||||
uint8 constant shiftRedistributionPeriod = 0;
|
// uint8 constant shiftRedistributionPeriod = 0;
|
||||||
uint256 constant maskRedistributionPeriod = 0x00000000000000000000000000000000000000000000000000000000ffffffff; // (1 << 32) - 1
|
// uint256 constant maskRedistributionPeriod = 0x00000000000000000000000000000000000000000000000000000000ffffffff; // (1 << 32) - 1
|
||||||
uint8 constant shiftRedistributionValue = 32;
|
// uint8 constant shiftRedistributionValue = 32;
|
||||||
uint256 constant maskRedistributionValue = 0x00000000000000000000000000000000000000ffffffffffffffffff00000000; // ((1 << 72) - 1) << 32
|
// uint256 constant maskRedistributionValue = 0x00000000000000000000000000000000000000ffffffffffffffffff00000000; // ((1 << 72) - 1) << 32
|
||||||
uint8 constant shiftRedistributionDemurrage = 104;
|
// uint8 constant shiftRedistributionDemurrage = 104;
|
||||||
uint256 constant maskRedistributionDemurrage = 0x0000000000ffffffffffffffffffffffffffff00000000000000000000000000; // ((1 << 20) - 1) << 140
|
// uint256 constant maskRedistributionDemurrage = 0x0000000000ffffffffffffffffffffffffffff00000000000000000000000000; // ((1 << 36) - 1) << 140
|
||||||
|
|
||||||
// Account balances
|
// Account balances
|
||||||
mapping (address => uint256) account;
|
mapping (address => uint256) account;
|
||||||
@@ -20,8 +26,6 @@ contract DemurrageTokenSingleCap {
|
|||||||
// Cached demurrage amount, ppm with 38 digit resolution
|
// Cached demurrage amount, ppm with 38 digit resolution
|
||||||
uint128 public demurrageAmount;
|
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
|
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
|
||||||
uint256 public demurrageTimestamp;
|
uint256 public demurrageTimestamp;
|
||||||
|
|
||||||
@@ -40,10 +44,17 @@ contract DemurrageTokenSingleCap {
|
|||||||
uint256 public decimals;
|
uint256 public decimals;
|
||||||
|
|
||||||
// Implements ERC20
|
// Implements ERC20
|
||||||
uint256 public totalSupply;
|
//uint256 public totalSupply;
|
||||||
|
uint256 supply;
|
||||||
|
|
||||||
// Minimum amount of (demurraged) tokens an account must spend to participate in redistribution for a particular period
|
// Last executed period
|
||||||
uint256 public minimumParticipantSpend;
|
uint256 public lastPeriod;
|
||||||
|
|
||||||
|
// Last sink redistribution amount
|
||||||
|
uint256 public totalSink;
|
||||||
|
|
||||||
|
// Value of burnt tokens (burnt tokens do not decay)
|
||||||
|
uint256 public burned;
|
||||||
|
|
||||||
// 128 bit resolution of the demurrage divisor
|
// 128 bit resolution of the demurrage divisor
|
||||||
// (this constant x 1000000 is contained within 128 bits)
|
// (this constant x 1000000 is contained within 128 bits)
|
||||||
@@ -71,7 +82,7 @@ contract DemurrageTokenSingleCap {
|
|||||||
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
|
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
|
||||||
|
|
||||||
// Address to send unallocated redistribution tokens
|
// Address to send unallocated redistribution tokens
|
||||||
address sinkAddress;
|
address public sinkAddress;
|
||||||
|
|
||||||
// Implements ERC20
|
// Implements ERC20
|
||||||
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||||
@@ -94,10 +105,15 @@ contract DemurrageTokenSingleCap {
|
|||||||
// Temporary event used in development, will be removed on prod
|
// Temporary event used in development, will be removed on prod
|
||||||
event Debug(bytes32 _foo);
|
event Debug(bytes32 _foo);
|
||||||
|
|
||||||
|
// Emitted when tokens are burned
|
||||||
|
event Burn(address indexed _burner, uint256 _value);
|
||||||
|
|
||||||
// EIP173
|
// EIP173
|
||||||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
|
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
|
||||||
|
|
||||||
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint128 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress) public {
|
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint128 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress) public {
|
||||||
|
redistributionItem memory initialRedistribution;
|
||||||
|
|
||||||
// ACL setup
|
// ACL setup
|
||||||
owner = msg.sender;
|
owner = msg.sender;
|
||||||
minter[owner] = true;
|
minter[owner] = true;
|
||||||
@@ -111,17 +127,13 @@ contract DemurrageTokenSingleCap {
|
|||||||
demurrageTimestamp = block.timestamp;
|
demurrageTimestamp = block.timestamp;
|
||||||
periodStart = demurrageTimestamp;
|
periodStart = demurrageTimestamp;
|
||||||
periodDuration = _periodMinutes * 60;
|
periodDuration = _periodMinutes * 60;
|
||||||
//demurrageAmount = 100000000000000000000000000000000000000 - _taxLevelMinute; // Represents 38 decimal places, same as resolutionFactor
|
demurrageAmount = uint128(nanoDivider) * 100;
|
||||||
//demurrageAmount = 100000000000000000000000000000000000000;
|
|
||||||
demurrageAmount = 10000000000000000000000000000;
|
|
||||||
//demurragePeriod = 1;
|
|
||||||
taxLevel = _taxLevelMinute; // Represents 38 decimal places
|
taxLevel = _taxLevelMinute; // Represents 38 decimal places
|
||||||
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
|
initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
|
||||||
redistributions.push(initialRedistribution);
|
redistributions.push(initialRedistribution);
|
||||||
|
|
||||||
// Misc settings
|
// Misc settings
|
||||||
sinkAddress = _defaultSinkAddress;
|
sinkAddress = _defaultSinkAddress;
|
||||||
minimumParticipantSpend = 10 ** uint256(_decimals);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -153,10 +165,8 @@ contract DemurrageTokenSingleCap {
|
|||||||
|
|
||||||
baseBalance = baseBalanceOf(_account);
|
baseBalance = baseBalanceOf(_account);
|
||||||
|
|
||||||
//periodCount = actualPeriod() - demurragePeriod;
|
|
||||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||||
|
|
||||||
//currentDemurragedAmount = uint128(decayBy(demurrageAmount, periodCount));
|
|
||||||
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
|
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
|
||||||
|
|
||||||
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
|
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
|
||||||
@@ -211,7 +221,7 @@ contract DemurrageTokenSingleCap {
|
|||||||
|
|
||||||
changePeriod();
|
changePeriod();
|
||||||
baseAmount = toBaseAmount(_amount);
|
baseAmount = toBaseAmount(_amount);
|
||||||
totalSupply += _amount;
|
supply += _amount;
|
||||||
increaseBaseBalance(_beneficiary, baseAmount);
|
increaseBaseBalance(_beneficiary, baseAmount);
|
||||||
emit Mint(msg.sender, _beneficiary, _amount);
|
emit Mint(msg.sender, _beneficiary, _amount);
|
||||||
saveRedistributionSupply();
|
saveRedistributionSupply();
|
||||||
@@ -220,47 +230,82 @@ contract DemurrageTokenSingleCap {
|
|||||||
|
|
||||||
// Deserializes the redistribution word
|
// Deserializes the redistribution word
|
||||||
// uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
|
// uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
|
||||||
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) public pure returns(bytes32) {
|
// function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) public pure returns(bytes32) {
|
||||||
bytes32 redistribution;
|
// bytes32 redistribution;
|
||||||
|
//
|
||||||
|
// redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
|
||||||
|
// redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
|
||||||
|
// redistribution |= bytes32(_period & maskRedistributionPeriod);
|
||||||
|
// return redistribution;
|
||||||
|
// }
|
||||||
|
|
||||||
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
|
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) public pure returns(redistributionItem memory) {
|
||||||
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
|
redistributionItem memory redistribution;
|
||||||
redistribution |= bytes32(_period & maskRedistributionPeriod);
|
|
||||||
|
redistribution.period = uint32(_period);
|
||||||
|
redistribution.value = uint72(_value);
|
||||||
|
redistribution.demurrage = uint104(_demurrageModifierPpm);
|
||||||
return redistribution;
|
return redistribution;
|
||||||
|
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// // Serializes the demurrage period part of the redistribution word
|
||||||
|
// function toRedistributionPeriod(bytes32 redistribution) public pure returns (uint256) {
|
||||||
|
// return uint256(redistribution) & maskRedistributionPeriod;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
|
||||||
|
function toRedistributionPeriod(redistributionItem memory _redistribution) public pure returns (uint256) {
|
||||||
|
return uint256(_redistribution.period);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializes the demurrage period part of the redistribution word
|
// // Serializes the supply part of the redistribution word
|
||||||
function toRedistributionPeriod(bytes32 redistribution) public pure returns (uint256) {
|
// function toRedistributionSupply(bytes32 redistribution) public pure returns (uint256) {
|
||||||
return uint256(redistribution) & maskRedistributionPeriod;
|
// return (uint256(redistribution) & kkRedistributionValue) >> shiftRedistributionValue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
function toRedistributionSupply(redistributionItem memory _redistribution) public pure returns (uint256) {
|
||||||
|
return uint256(_redistribution.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializes the supply part of the redistribution word
|
//
|
||||||
function toRedistributionSupply(bytes32 redistribution) public pure returns (uint256) {
|
// // Serializes the number of participants part of the redistribution word
|
||||||
return (uint256(redistribution) & maskRedistributionValue) >> shiftRedistributionValue;
|
// function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
|
||||||
|
// return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
|
||||||
|
// }
|
||||||
|
|
||||||
|
function toRedistributionDemurrageModifier(redistributionItem memory _redistribution) public pure returns (uint256) {
|
||||||
|
return uint256(_redistribution.demurrage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serializes the number of participants part of the redistribution word
|
|
||||||
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
|
|
||||||
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client accessor to the redistributions array length
|
// Client accessor to the redistributions array length
|
||||||
function redistributionCount() public view returns (uint256) {
|
function redistributionCount() public view returns (uint256) {
|
||||||
return redistributions.length;
|
return redistributions.length;
|
||||||
}
|
}
|
||||||
|
//
|
||||||
// Save the current total supply amount to the current redistribution period
|
// // Save the current total supply amount to the current redistribution period
|
||||||
|
// function saveRedistributionSupply() private returns (bool) {
|
||||||
|
// uint256 currentRedistribution;
|
||||||
|
// uint256 grownSupply;
|
||||||
|
//
|
||||||
|
// grownSupply = totalSupply();
|
||||||
|
// currentRedistribution = uint256(redistributions[redistributions.length-1]);
|
||||||
|
// currentRedistribution &= (~maskRedistributionValue);
|
||||||
|
// currentRedistribution |= (grownSupply << shiftRedistributionValue);
|
||||||
|
//
|
||||||
|
// redistributions[redistributions.length-1] = bytes32(currentRedistribution);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
function saveRedistributionSupply() private returns (bool) {
|
function saveRedistributionSupply() private returns (bool) {
|
||||||
uint256 currentRedistribution;
|
redistributionItem memory currentRedistribution;
|
||||||
uint256 grownSupply;
|
uint256 grownSupply;
|
||||||
|
|
||||||
//grownSupply = growBy(totalSupply, 1);
|
grownSupply = totalSupply();
|
||||||
grownSupply = totalSupply;
|
currentRedistribution = redistributions[redistributions.length-1];
|
||||||
currentRedistribution = uint256(redistributions[redistributions.length-1]);
|
currentRedistribution.value = uint72(grownSupply);
|
||||||
currentRedistribution &= (~maskRedistributionValue);
|
|
||||||
currentRedistribution |= (grownSupply << shiftRedistributionValue);
|
|
||||||
|
|
||||||
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
|
redistributions[redistributions.length-1] = currentRedistribution;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,15 +314,16 @@ contract DemurrageTokenSingleCap {
|
|||||||
return uint128((block.timestamp - periodStart) / periodDuration + 1);
|
return uint128((block.timestamp - periodStart) / periodDuration + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an entered demurrage period to the redistribution array
|
// Retrieve next redistribution if the period threshold has been crossed
|
||||||
function checkPeriod() private view returns (bytes32) {
|
function checkPeriod() private view returns (redistributionItem memory) {
|
||||||
bytes32 lastRedistribution;
|
redistributionItem memory lastRedistribution;
|
||||||
|
redistributionItem memory emptyRedistribution;
|
||||||
uint256 currentPeriod;
|
uint256 currentPeriod;
|
||||||
|
|
||||||
lastRedistribution = redistributions[redistributions.length-1];
|
lastRedistribution = redistributions[lastPeriod];
|
||||||
currentPeriod = this.actualPeriod();
|
currentPeriod = this.actualPeriod();
|
||||||
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
|
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
|
||||||
return bytes32(0x00);
|
return emptyRedistribution;
|
||||||
}
|
}
|
||||||
return lastRedistribution;
|
return lastRedistribution;
|
||||||
}
|
}
|
||||||
@@ -285,12 +331,11 @@ contract DemurrageTokenSingleCap {
|
|||||||
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
|
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
|
||||||
uint256 difference;
|
uint256 difference;
|
||||||
|
|
||||||
//difference = _supply * (resolutionFactor - _demurrageAmount); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
|
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000));
|
||||||
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000)); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
|
|
||||||
return difference / resolutionFactor;
|
return difference / resolutionFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDistributionFromRedistribution(bytes32 _redistribution) public returns (uint256) {
|
function getDistributionFromRedistribution(redistributionItem memory _redistribution) public returns (uint256) {
|
||||||
uint256 redistributionSupply;
|
uint256 redistributionSupply;
|
||||||
uint256 redistributionDemurrage;
|
uint256 redistributionDemurrage;
|
||||||
|
|
||||||
@@ -300,11 +345,15 @@ contract DemurrageTokenSingleCap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns the amount sent to the sink address
|
// Returns the amount sent to the sink address
|
||||||
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
|
function applyDefaultRedistribution(redistributionItem memory _redistribution) private returns (uint256) {
|
||||||
uint256 unit;
|
uint256 unit;
|
||||||
|
uint256 baseUnit;
|
||||||
|
|
||||||
unit = getDistributionFromRedistribution(_redistribution);
|
unit = getDistributionFromRedistribution(_redistribution);
|
||||||
increaseBaseBalance(sinkAddress, toBaseAmount(unit));
|
baseUnit = toBaseAmount(unit) - totalSink;
|
||||||
|
increaseBaseBalance(sinkAddress, baseUnit);
|
||||||
|
lastPeriod += 1;
|
||||||
|
totalSink += baseUnit;
|
||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,13 +368,9 @@ contract DemurrageTokenSingleCap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
|
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
|
||||||
//uint128 epochPeriodCount;
|
|
||||||
uint256 periodCount;
|
uint256 periodCount;
|
||||||
uint256 lastDemurrageAmount;
|
uint256 lastDemurrageAmount;
|
||||||
|
|
||||||
//epochPeriodCount = actualPeriod();
|
|
||||||
//periodCount = epochPeriodCount - demurragePeriod;
|
|
||||||
|
|
||||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||||
if (periodCount == 0) {
|
if (periodCount == 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -355,37 +400,46 @@ contract DemurrageTokenSingleCap {
|
|||||||
return (block.timestamp - _target) / 60;
|
return (block.timestamp - _target) / 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEmptyRedistribution(redistributionItem memory _redistribution) public pure returns(bool) {
|
||||||
|
if (_redistribution.period > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_redistribution.value > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_redistribution.demurrage > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Recalculate the demurrage modifier for the new period
|
// 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) {
|
function changePeriod() public returns (bool) {
|
||||||
bytes32 currentRedistribution;
|
redistributionItem memory currentRedistribution;
|
||||||
bytes32 nextRedistribution;
|
redistributionItem memory nextRedistribution;
|
||||||
|
redistributionItem memory lastRedistribution;
|
||||||
uint256 currentPeriod;
|
uint256 currentPeriod;
|
||||||
uint256 currentDemurrageAmount;
|
uint256 lastDemurrageAmount;
|
||||||
uint256 nextRedistributionDemurrage;
|
uint256 nextRedistributionDemurrage;
|
||||||
uint256 demurrageCounts;
|
uint256 demurrageCounts;
|
||||||
uint256 periodTimestamp;
|
|
||||||
uint256 nextPeriod;
|
uint256 nextPeriod;
|
||||||
|
|
||||||
applyDemurrage();
|
applyDemurrage();
|
||||||
currentRedistribution = checkPeriod();
|
currentRedistribution = checkPeriod();
|
||||||
if (currentRedistribution == bytes32(0x00)) {
|
if (isEmptyRedistribution(currentRedistribution)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate the decay from previous redistributino
|
||||||
|
lastRedistribution = redistributions[lastPeriod];
|
||||||
currentPeriod = toRedistributionPeriod(currentRedistribution);
|
currentPeriod = toRedistributionPeriod(currentRedistribution);
|
||||||
nextPeriod = currentPeriod + 1;
|
nextPeriod = currentPeriod + 1;
|
||||||
periodTimestamp = getPeriodTimeDelta(currentPeriod);
|
lastDemurrageAmount = toRedistributionDemurrageModifier(lastRedistribution);
|
||||||
|
demurrageCounts = periodDuration / 60;
|
||||||
currentDemurrageAmount = demurrageAmount;
|
nextRedistributionDemurrage = decayBy(lastDemurrageAmount, demurrageCounts);
|
||||||
|
|
||||||
demurrageCounts = demurrageCycles(periodTimestamp);
|
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply(), nextPeriod);
|
||||||
if (demurrageCounts > 0) {
|
|
||||||
nextRedistributionDemurrage = growBy(currentDemurrageAmount, demurrageCounts);
|
|
||||||
} else {
|
|
||||||
nextRedistributionDemurrage = currentDemurrageAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, nextPeriod);
|
|
||||||
redistributions.push(nextRedistribution);
|
redistributions.push(nextRedistribution);
|
||||||
|
|
||||||
applyDefaultRedistribution(nextRedistribution);
|
applyDefaultRedistribution(nextRedistribution);
|
||||||
@@ -394,18 +448,18 @@ contract DemurrageTokenSingleCap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reverse a value reduced by demurrage by the given period to its original value
|
// Reverse a value reduced by demurrage by the given period to its original value
|
||||||
function growBy(uint256 _value, uint256 _period) public view returns (uint256) {
|
// function growBy(uint256 _value, uint256 _period) public view returns (uint256) {
|
||||||
uint256 valueFactor;
|
// uint256 valueFactor;
|
||||||
uint256 truncatedTaxLevel;
|
// uint256 truncatedTaxLevel;
|
||||||
|
//
|
||||||
valueFactor = growthResolutionFactor;
|
// valueFactor = growthResolutionFactor;
|
||||||
truncatedTaxLevel = taxLevel / nanoDivider;
|
// truncatedTaxLevel = taxLevel / nanoDivider;
|
||||||
|
//
|
||||||
for (uint256 i = 0; i < _period; i++) {
|
// for (uint256 i = 0; i < _period; i++) {
|
||||||
valueFactor = valueFactor + ((valueFactor * truncatedTaxLevel) / growthResolutionFactor);
|
// valueFactor = valueFactor + ((valueFactor * truncatedTaxLevel) / growthResolutionFactor);
|
||||||
}
|
// }
|
||||||
return (valueFactor * _value) / growthResolutionFactor;
|
// return (valueFactor * _value) / growthResolutionFactor;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Calculate a value reduced by demurrage by the given period
|
// Calculate a value reduced by demurrage by the given period
|
||||||
function decayBy(uint256 _value, uint256 _period) public view returns (uint256) {
|
function decayBy(uint256 _value, uint256 _period) public view returns (uint256) {
|
||||||
@@ -423,7 +477,6 @@ contract DemurrageTokenSingleCap {
|
|||||||
|
|
||||||
// Inflates the given amount according to the current demurrage modifier
|
// Inflates the given amount according to the current demurrage modifier
|
||||||
function toBaseAmount(uint256 _value) public view returns (uint256) {
|
function toBaseAmount(uint256 _value) public view returns (uint256) {
|
||||||
//return (_value * resolutionFactor) / demurrageAmount;
|
|
||||||
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
|
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,14 +484,45 @@ contract DemurrageTokenSingleCap {
|
|||||||
function approve(address _spender, uint256 _value) public returns (bool) {
|
function approve(address _spender, uint256 _value) public returns (bool) {
|
||||||
uint256 baseValue;
|
uint256 baseValue;
|
||||||
|
|
||||||
|
if (allowance[msg.sender][_spender] > 0) {
|
||||||
|
require(_value == 0, 'ZERO_FIRST');
|
||||||
|
}
|
||||||
|
|
||||||
changePeriod();
|
changePeriod();
|
||||||
|
|
||||||
baseValue = toBaseAmount(_value);
|
baseValue = toBaseAmount(_value);
|
||||||
allowance[msg.sender][_spender] += baseValue;
|
allowance[msg.sender][_spender] = baseValue;
|
||||||
emit Approval(msg.sender, _spender, _value);
|
emit Approval(msg.sender, _spender, _value);
|
||||||
return true;
|
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]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Implements ERC20, triggers tax and/or redistribution
|
// Implements ERC20, triggers tax and/or redistribution
|
||||||
function transfer(address _to, uint256 _value) public returns (bool) {
|
function transfer(address _to, uint256 _value) public returns (bool) {
|
||||||
uint256 baseValue;
|
uint256 baseValue;
|
||||||
@@ -462,7 +546,9 @@ contract DemurrageTokenSingleCap {
|
|||||||
baseValue = toBaseAmount(_value);
|
baseValue = toBaseAmount(_value);
|
||||||
require(allowance[_from][msg.sender] >= baseValue);
|
require(allowance[_from][msg.sender] >= baseValue);
|
||||||
|
|
||||||
|
allowance[_from][msg.sender] -= baseValue;
|
||||||
result = transferBase(_from, _to, baseValue);
|
result = transferBase(_from, _to, baseValue);
|
||||||
|
|
||||||
emit Transfer(_from, _to, _value);
|
emit Transfer(_from, _to, _value);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -474,7 +560,6 @@ contract DemurrageTokenSingleCap {
|
|||||||
decreaseBaseBalance(_from, _value);
|
decreaseBaseBalance(_from, _value);
|
||||||
increaseBaseBalance(_to, _value);
|
increaseBaseBalance(_to, _value);
|
||||||
|
|
||||||
//period = actualPeriod();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,6 +580,29 @@ contract DemurrageTokenSingleCap {
|
|||||||
emit OwnershipTransferred(oldOwner, owner);
|
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
|
// Implements EIP165
|
||||||
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
||||||
if (_sum == 0xc6bb4b70) { // ERC20
|
if (_sum == 0xc6bb4b70) { // ERC20
|
||||||
|
|||||||
@@ -32,7 +32,15 @@ test: all
|
|||||||
python ../python/tests/test_redistribution.py
|
python ../python/tests/test_redistribution.py
|
||||||
python ../python/tests/test_pure.py
|
python ../python/tests/test_pure.py
|
||||||
|
|
||||||
install: all
|
#install: all
|
||||||
|
install: single_nocap
|
||||||
|
#cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
|
||||||
|
cp -v DemurrageTokenSingleNocap.json ../python/erc20_demurrage_token/data/
|
||||||
|
cp -v DemurrageTokenSingleNocap.bin ../python/erc20_demurrage_token/data/
|
||||||
|
|
||||||
|
install-broken: all
|
||||||
cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
|
cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: test install
|
.PHONY: test install
|
||||||
|
|||||||
Reference in New Issue
Block a user