Compare commits
9 Commits
0.0.2-dev
...
lash/add-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43b3d2b488
|
||
|
|
0e1613c5f6
|
||
|
|
899efb65fc
|
||
|
|
f84edb5f3b
|
||
|
|
c6b5d9a8e0
|
||
|
|
abe82949ea
|
||
|
|
a6f53e7278
|
||
|
|
98c460dc2f
|
||
|
|
00bb87e3ec
|
@@ -1 +1 @@
|
|||||||
include erc20_demurrage_token/data/*
|
include erc20_demurrage_token/data/* erc20_demurrage_token/data/config/*.ini *requirements.txt
|
||||||
|
|||||||
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
68
python/erc20_demurrage_token/demurrage.py
Normal file
68
python/erc20_demurrage_token/demurrage.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
import datetime
|
||||||
|
import math
|
||||||
|
|
||||||
|
# eternal imports
|
||||||
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from .token import DemurrageToken
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logg = logging.getLogger()
|
||||||
|
|
||||||
|
|
||||||
|
class DemurrageCalculator:
|
||||||
|
|
||||||
|
def __init__(self, interest_f_minute):
|
||||||
|
|
||||||
|
self.r_min = interest_f_minute
|
||||||
|
self.r_hour = 1 - ((1 -self.r_min) ** 60)
|
||||||
|
self.r_day = 1 - ((1 -self.r_hour) ** 24)
|
||||||
|
#self.r_week = interest_f_day ** 7
|
||||||
|
logg.info('demurrage calculator set with min {:.32f} hour {:.32f} day {:.32f}'.format(self.r_min, self.r_hour, self.r_day))
|
||||||
|
|
||||||
|
|
||||||
|
def amount_since(self, amount, timestamp):
|
||||||
|
delta = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(timestamp)
|
||||||
|
adjusted_amount = amount * ((1 - self.r_day) ** (delta.days))
|
||||||
|
logg.debug('adjusted for {} days {} -> {}'.format(delta.days, amount, adjusted_amount))
|
||||||
|
|
||||||
|
remainder = delta.seconds
|
||||||
|
remainder_hours = math.floor(remainder / (60 * 60))
|
||||||
|
adjusted_delta = adjusted_amount * ((1 - self.r_hour) ** remainder_hours)
|
||||||
|
adjusted_amount -= (adjusted_amount - adjusted_delta)
|
||||||
|
logg.debug('adjusted for {} hours {} -> {} delta {}'.format(remainder_hours, amount, adjusted_amount, adjusted_delta))
|
||||||
|
|
||||||
|
remainder -= (remainder_hours * (60 * 60))
|
||||||
|
remainder_minutes = math.floor(remainder / 60)
|
||||||
|
adjusted_delta = adjusted_amount * ((1 - self.r_min) ** remainder_minutes)
|
||||||
|
adjusted_amount -= (adjusted_amount - adjusted_delta)
|
||||||
|
logg.debug('adjusted for {} minutes {} -> {} delta {}'.format(remainder_minutes, amount, adjusted_amount, adjusted_delta))
|
||||||
|
|
||||||
|
return adjusted_amount
|
||||||
|
|
||||||
|
|
||||||
|
def amount_since_slow(self, amount, timestamp):
|
||||||
|
delta = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(timestamp)
|
||||||
|
remainder_minutes = math.floor(delta.total_seconds() / 60)
|
||||||
|
adjusted_amount = amount * ((1 - self.r_min) ** remainder_minutes)
|
||||||
|
logg.debug('adjusted for {} minutes {} -> {} delta {}'.format(remainder_minutes, amount, adjusted_amount, amount - adjusted_amount))
|
||||||
|
|
||||||
|
return adjusted_amount
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_contract(rpc, chain_spec, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
|
c = DemurrageToken(chain_spec)
|
||||||
|
o = c.tax_level(contract_address, sender_address=sender_address)
|
||||||
|
r = rpc.do(o)
|
||||||
|
taxlevel_i = c.parse_tax_level(r)
|
||||||
|
|
||||||
|
o = c.resolution_factor(contract_address, sender_address=sender_address)
|
||||||
|
r = rpc.do(o)
|
||||||
|
divider = c.parse_resolution_factor(r)
|
||||||
|
logg.debug('taxlevel {} f {}'.format(taxlevel_i, divider))
|
||||||
|
taxlevel_f = taxlevel_i / divider
|
||||||
|
return DemurrageCalculator(taxlevel_f)
|
||||||
@@ -28,8 +28,10 @@ from chainlib.eth.gas import (
|
|||||||
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
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
|
import erc20_demurrage_token
|
||||||
from erc20_demurrage_token import (
|
from erc20_demurrage_token import (
|
||||||
DemurrageToken,
|
DemurrageToken,
|
||||||
DemurrageTokenSettings,
|
DemurrageTokenSettings,
|
||||||
@@ -41,56 +43,37 @@ logg = logging.getLogger()
|
|||||||
script_dir = os.path.dirname(__file__)
|
script_dir = os.path.dirname(__file__)
|
||||||
data_dir = os.path.join(script_dir, '..', 'data')
|
data_dir = os.path.join(script_dir, '..', 'data')
|
||||||
|
|
||||||
default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/share/sarafu-token')
|
config_dir = os.path.join(data_dir, 'config')
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser()
|
arg_flags = chainlib.eth.cli.argflag_std_write
|
||||||
argparser.add_argument('-c', '--config', dest='c', type=str, default=default_config_dir, help='configuration directory')
|
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||||
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
|
argparser.add_argument('--name', dest='token_name', type=str, help='Token name')
|
||||||
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
|
argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol')
|
||||||
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
|
argparser.add_argument('--decimals', dest='token_decimals', default=18, type=int, help='Token decimals')
|
||||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
|
||||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
|
||||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
|
||||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
|
||||||
argparser.add_argument('-d', action='store_true', help='Dump RPC calls to terminal and do not send')
|
|
||||||
argparser.add_argument('--name', type=str, help='Token name')
|
|
||||||
argparser.add_argument('--decimals', default=6, type=int, help='Token decimals')
|
|
||||||
argparser.add_argument('--gas-price', type=int, dest='gas_price', help='Override gas price')
|
|
||||||
argparser.add_argument('--nonce', type=int, help='Override transaction nonce')
|
|
||||||
argparser.add_argument('--sink-address', dest='sink_address', default=ZERO_ADDRESS, type=str, help='demurrage level,ppm per minute')
|
argparser.add_argument('--sink-address', dest='sink_address', default=ZERO_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', type=int, help='redistribution period, minutes (0 = deactivate)') # default 10080 = week
|
||||||
argparser.add_argument('--env-prefix', default=os.environ.get('CONFINI_ENV_PREFIX'), dest='env_prefix', type=str, help='environment prefix for variables to overwrite configuration')
|
argparser.add_argument('--multi', action='store_true', help='automatic redistribution')
|
||||||
argparser.add_argument('--symbol', type=str, help='Token symbol')
|
|
||||||
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()
|
||||||
|
|
||||||
if args.vv:
|
arg_flags = chainlib.eth.cli.argflag_std_write
|
||||||
logg.setLevel(logging.DEBUG)
|
|
||||||
elif args.v:
|
|
||||||
logg.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
block_all = args.ww
|
extra_args = {
|
||||||
block_last = args.w or block_all
|
'redistribution_period': 'TOKEN_REDISTRIBUTION_PERIOD',
|
||||||
|
'demurrage_level': 'TOKEN_DEMURRAGE_LEVEL',
|
||||||
# process config
|
'supply_limit': 'TOKEN_SUPPLY_LIMIT',
|
||||||
config = confini.Config(args.c)
|
'token_name': 'TOKEN_NAME',
|
||||||
config.process()
|
'token_symbol': 'TOKEN_SYMBOL',
|
||||||
args_override = {
|
'token_decimals': 'TOKEN_DECIMALS',
|
||||||
'TOKEN_REDISTRIBUTION_PERIOD': getattr(args, 'redistribution_period'),
|
'sink_address': 'TOKEN_SINK_ADDRESS',
|
||||||
'TOKEN_DEMURRAGE_LEVEL': getattr(args, 'demurrage_level'),
|
'multi': None,
|
||||||
'TOKEN_SUPPLY_LIMIT': getattr(args, 'supply_limit'),
|
|
||||||
'TOKEN_SYMBOL': getattr(args, 'symbol'),
|
|
||||||
'TOKEN_NAME': getattr(args, 'name'),
|
|
||||||
'TOKEN_DECIMALS': getattr(args, 'decimals'),
|
|
||||||
'TOKEN_SINK_ADDRESS': getattr(args, 'sink_address'),
|
|
||||||
'SESSION_CHAIN_SPEC': getattr(args, 'i'),
|
|
||||||
'ETH_PROVIDER': getattr(args, 'p'),
|
|
||||||
}
|
}
|
||||||
if config.get('TOKEN_NAME') == 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')))
|
logg.info('token name not set, using symbol {} as name'.format(config.get('TOKEN_SYMBOL')))
|
||||||
config.add(config.get('TOKEN_SYMBOL'), 'TOKEN_NAME', True)
|
config.add(config.get('TOKEN_SYMBOL'), 'TOKEN_NAME', True)
|
||||||
config.dict_override(args_override, 'cli args')
|
|
||||||
|
|
||||||
if config.get('TOKEN_SUPPLY_LIMIT') == None:
|
if config.get('TOKEN_SUPPLY_LIMIT') == None:
|
||||||
config.add(0, 'TOKEN_SUPPLY_LIMIT', True)
|
config.add(0, 'TOKEN_SUPPLY_LIMIT', True)
|
||||||
@@ -99,44 +82,22 @@ if config.get('TOKEN_REDISTRIBUTION_PERIOD') == None:
|
|||||||
config.add(10800, 'TOKEN_REDISTRIBUTION_PERIOD', True)
|
config.add(10800, 'TOKEN_REDISTRIBUTION_PERIOD', True)
|
||||||
logg.debug('config loaded:\n{}'.format(config))
|
logg.debug('config loaded:\n{}'.format(config))
|
||||||
|
|
||||||
passphrase_env = 'ETH_PASSPHRASE'
|
wallet = chainlib.eth.cli.Wallet()
|
||||||
if args.env_prefix != None:
|
wallet.from_config(config)
|
||||||
passphrase_env = args.env_prefix + '_' + passphrase_env
|
|
||||||
passphrase = os.environ.get(passphrase_env)
|
|
||||||
if passphrase == None:
|
|
||||||
logg.warning('no passphrase given')
|
|
||||||
passphrase=''
|
|
||||||
|
|
||||||
signer_address = None
|
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||||
keystore = DictKeystore()
|
conn = rpc.connect_by_config(config)
|
||||||
if args.y != None:
|
|
||||||
logg.debug('loading keystore file {}'.format(args.y))
|
|
||||||
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
|
||||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
|
||||||
signer = EIP155Signer(keystore)
|
|
||||||
|
|
||||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||||
|
|
||||||
rpc = EthHTTPConnection(args.p)
|
|
||||||
nonce_oracle = None
|
|
||||||
if args.nonce != None:
|
|
||||||
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
|
||||||
else:
|
|
||||||
nonce_oracle = RPCNonceOracle(signer_address, rpc)
|
|
||||||
|
|
||||||
gas_oracle = None
|
|
||||||
if args.gas_price !=None:
|
|
||||||
gas_oracle = OverrideGasOracle(price=args.gas_price, conn=rpc, code_callback=DemurrageToken.gas)
|
|
||||||
else:
|
|
||||||
gas_oracle = RPCGasOracle(rpc, code_callback=DemurrageToken.gas)
|
|
||||||
|
|
||||||
dummy = args.d
|
|
||||||
|
|
||||||
token_name = args.name
|
|
||||||
if token_name == None:
|
|
||||||
token_name = args.symbol
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
signer = rpc.get_signer()
|
||||||
|
signer_address = rpc.get_sender_address()
|
||||||
|
|
||||||
|
gas_oracle = rpc.get_gas_oracle()
|
||||||
|
nonce_oracle = rpc.get_nonce_oracle()
|
||||||
|
|
||||||
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
settings = DemurrageTokenSettings()
|
settings = DemurrageTokenSettings()
|
||||||
settings.name = config.get('TOKEN_NAME')
|
settings.name = config.get('TOKEN_NAME')
|
||||||
@@ -149,16 +110,13 @@ def main():
|
|||||||
(tx_hash_hex, o) = c.constructor(
|
(tx_hash_hex, o) = c.constructor(
|
||||||
signer_address,
|
signer_address,
|
||||||
settings,
|
settings,
|
||||||
redistribute=settings.period_minutes > 0,
|
redistribute=config.true('_MULTI'),
|
||||||
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
|
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
|
||||||
)
|
)
|
||||||
if dummy:
|
if config.get('_RPC_SEND'):
|
||||||
print(tx_hash_hex)
|
conn.do(o)
|
||||||
print(o)
|
if config.get('_WAIT'):
|
||||||
else:
|
r = conn.wait(tx_hash_hex)
|
||||||
rpc.do(o)
|
|
||||||
if block_last:
|
|
||||||
r = rpc.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')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -169,6 +127,9 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print(tx_hash_hex)
|
print(tx_hash_hex)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(o)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -345,6 +345,14 @@ class DemurrageToken(ERC20):
|
|||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
|
||||||
|
def tax_level(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
|
return self.call_noarg('taxLevel', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
|
|
||||||
|
def resolution_factor(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
|
return self.call_noarg('resolutionFactor', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
|
|
||||||
def actual_period(self, contract_address, sender_address=ZERO_ADDRESS):
|
def actual_period(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
return self.call_noarg('actualPeriod', contract_address, sender_address=sender_address)
|
return self.call_noarg('actualPeriod', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
@@ -507,3 +515,13 @@ class DemurrageToken(ERC20):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def parse_get_distribution(self, v):
|
def parse_get_distribution(self, v):
|
||||||
return abi_decode_single(ABIContractType.UINT256, v)
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_tax_level(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_resolution_factor(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|||||||
1
python/erc20_demurrage_token/unittest/__init__.py
Normal file
1
python/erc20_demurrage_token/unittest/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .base import *
|
||||||
@@ -12,6 +12,7 @@ from chainlib.eth.block import (
|
|||||||
block_by_number,
|
block_by_number,
|
||||||
)
|
)
|
||||||
from chainlib.eth.nonce import RPCNonceOracle
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from erc20_demurrage_token import (
|
from erc20_demurrage_token import (
|
||||||
@@ -29,64 +30,93 @@ TAX_LEVEL = int(10000 * 2) # 2%
|
|||||||
PERIOD = 10
|
PERIOD = 10
|
||||||
|
|
||||||
|
|
||||||
class TestDemurrage(EthTesterCase):
|
class TestTokenDeploy:
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestDemurrage, self).setUp()
|
|
||||||
|
|
||||||
|
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, supply=10**12):
|
||||||
self.tax_level = TAX_LEVEL
|
self.tax_level = TAX_LEVEL
|
||||||
self.period_seconds = PERIOD * 60
|
self.period_seconds = PERIOD * 60
|
||||||
|
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
|
||||||
self.settings = DemurrageTokenSettings()
|
self.settings = DemurrageTokenSettings()
|
||||||
self.settings.name = 'Foo Token'
|
self.settings.name = token_name
|
||||||
self.settings.symbol = 'FOO'
|
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 = self.accounts[9]
|
self.settings.sink_address = sink_address
|
||||||
self.sink_address = self.settings.sink_address
|
self.sink_address = self.settings.sink_address
|
||||||
|
|
||||||
o = block_latest()
|
o = block_latest()
|
||||||
self.start_block = self.rpc.do(o)
|
self.start_block = rpc.do(o)
|
||||||
|
|
||||||
o = block_by_number(self.start_block, include_tx=False)
|
o = block_by_number(self.start_block, include_tx=False)
|
||||||
r = self.rpc.do(o)
|
r = rpc.do(o)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.start_time = int(r['timestamp'], 16)
|
self.start_time = int(r['timestamp'], 16)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.start_time = int(r['timestamp'])
|
self.start_time = int(r['timestamp'])
|
||||||
|
|
||||||
self.default_supply = 10 ** 12
|
self.default_supply = supply
|
||||||
self.default_supply_cap = int(self.default_supply * 10)
|
self.default_supply_cap = int(self.default_supply * 10)
|
||||||
|
|
||||||
|
|
||||||
def deploy(self, interface, mode):
|
def deploy(self, rpc, deployer_address, interface, mode, supply_cap=10**12):
|
||||||
tx_hash = None
|
tx_hash = None
|
||||||
o = None
|
o = None
|
||||||
|
logg.debug('mode {} {}'.format(mode, self.settings))
|
||||||
|
self.mode = mode
|
||||||
if mode == 'MultiNocap':
|
if mode == 'MultiNocap':
|
||||||
(tx_hash, o) = interface.constructor(self.accounts[0], self.settings, redistribute=True, cap=0)
|
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=True, cap=0)
|
||||||
elif mode == 'SingleNocap':
|
elif mode == 'SingleNocap':
|
||||||
(tx_hash, o) = interface.constructor(self.accounts[0], self.settings, redistribute=False, cap=0)
|
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=0)
|
||||||
elif mode == 'MultiCap':
|
elif mode == 'MultiCap':
|
||||||
(tx_hash, o) = interface.constructor(self.accounts[0], self.settings, redistribute=True, cap=self.default_supply_cap)
|
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=True, cap=supply_cap)
|
||||||
elif mode == 'SingleCap':
|
elif mode == 'SingleCap':
|
||||||
(tx_hash, o) = interface.constructor(self.accounts[0], self.settings, redistribute=False, cap=self.default_supply_cap)
|
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=supply_cap)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Invalid mode "{}", valid are {}'.format(self.mode, DemurrageToken.valid_modes))
|
raise ValueError('Invalid mode "{}", valid are {}'.format(mode, DemurrageToken.valid_modes))
|
||||||
|
|
||||||
r = self.rpc.do(o)
|
r = rpc.do(o)
|
||||||
o = receipt(tx_hash)
|
o = receipt(tx_hash)
|
||||||
r = self.rpc.do(o)
|
r = rpc.do(o)
|
||||||
self.assertEqual(r['status'], 1)
|
assert r['status'] == 1
|
||||||
self.start_block = r['block_number']
|
self.start_block = r['block_number']
|
||||||
self.address = r['contract_address']
|
self.address = r['contract_address']
|
||||||
|
|
||||||
o = block_by_number(r['block_number'])
|
o = block_by_number(r['block_number'])
|
||||||
r = self.rpc.do(o)
|
r = rpc.do(o)
|
||||||
self.start_time = r['timestamp']
|
self.start_time = r['timestamp']
|
||||||
|
|
||||||
|
return self.address
|
||||||
|
|
||||||
|
|
||||||
|
class TestDemurrage(EthTesterCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDemurrage, self).setUp()
|
||||||
|
# token_deploy = TestTokenDeploy()
|
||||||
|
# self.settings = token_deploy.settings
|
||||||
|
# self.sink_address = token_deploy.sink_address
|
||||||
|
# self.start_block = token_deploy.start_block
|
||||||
|
# self.start_time = token_deploy.start_time
|
||||||
|
# self.default_supply = self.default_supply
|
||||||
|
# self.default_supply_cap = self.default_supply_cap
|
||||||
|
self.deployer = TestTokenDeploy(self.rpc)
|
||||||
|
self.default_supply = self.deployer.default_supply
|
||||||
|
self.default_supply_cap = self.deployer.default_supply_cap
|
||||||
|
self.start_block = None
|
||||||
|
self.address = None
|
||||||
|
self.start_time = None
|
||||||
|
|
||||||
|
|
||||||
|
def deploy(self, interface, mode):
|
||||||
|
self.address = self.deployer.deploy(self.rpc, self.accounts[0], interface, mode, supply_cap=self.default_supply_cap)
|
||||||
|
self.start_block = self.deployer.start_block
|
||||||
|
self.start_time = self.deployer.start_time
|
||||||
|
self.tax_level = self.deployer.tax_level
|
||||||
|
self.period_seconds = self.deployer.period_seconds
|
||||||
|
self.sink_address = self.deployer.sink_address
|
||||||
|
|
||||||
logg.debug('contract address {} start block {} start time {}'.format(self.address, self.start_block, self.start_time))
|
logg.debug('contract address {} start block {} start time {}'.format(self.address, self.start_block, self.start_time))
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
chainlib~=0.0.5a1
|
chainlib>=0.0.9a3,<=0.1.0
|
||||||
eth-erc20~=0.0.10a1
|
eth-erc20>=0.1.2a1,<0.2.0
|
||||||
crypto-dev-signer~=0.4.14b6
|
crypto-dev-signer>=0.4.15a1,<=0.4.15
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
export PYTHONPATH=.
|
export PYTHONPATH=.
|
||||||
@@ -16,7 +17,7 @@ done
|
|||||||
modes=(SingleCap) # other contracts need to be updted
|
modes=(SingleCap) # 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_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
|
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
|
||||||
done
|
done
|
||||||
|
|
||||||
modes=(MultiCap SingleCap)
|
modes=(MultiCap SingleCap)
|
||||||
@@ -31,3 +32,4 @@ done
|
|||||||
#done
|
#done
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
set +x
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = erc20-demurrage-token
|
name = erc20-demurrage-token
|
||||||
version = 0.0.2a1
|
version = 0.0.5a2
|
||||||
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
|
||||||
url = https://gitlab.com/grassrootseconomics/sarafu-token
|
url = https://gitlab.com/ccicnet/erc20-demurrage-token
|
||||||
keywords =
|
keywords =
|
||||||
ethereum
|
ethereum
|
||||||
blockchain
|
blockchain
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from chainlib.eth.tx import receipt
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageDefault
|
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from chainlib.eth.block import (
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageDefault
|
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from hexathon import (
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageCap
|
from erc20_demurrage_token.unittest.base import TestDemurrageCap
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
41
python/tests/test_demurrage_ext.py
Normal file
41
python/tests/test_demurrage_ext.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# standard imports
|
||||||
|
import datetime
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# external imports
|
||||||
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
class TestEmulate(TestDemurrage):
|
||||||
|
|
||||||
|
def test_amount_since(self):
|
||||||
|
d = datetime.datetime.utcnow() - datetime.timedelta(seconds=29, hours=5, minutes=3, days=4)
|
||||||
|
c = DemurrageCalculator(0.00000050105908373373)
|
||||||
|
a = c.amount_since(100, d.timestamp())
|
||||||
|
self.assert_within_lower(a, 99.69667, 0.1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_amount_since_slow(self):
|
||||||
|
d = datetime.datetime.utcnow() - datetime.timedelta(seconds=29, hours=5, minutes=3, days=4)
|
||||||
|
c = DemurrageCalculator(0.00000050105908373373)
|
||||||
|
a = c.amount_since_slow(100, d.timestamp())
|
||||||
|
self.assert_within_lower(a, 99.69667, 0.1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_contract(self):
|
||||||
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
|
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||||
|
self.deploy(c, 'SingleNocap')
|
||||||
|
dc = DemurrageCalculator.from_contract(self.rpc, self.chain_spec, self.address, sender_address=self.accounts[0])
|
||||||
|
self.assertEqual(dc.r_min, 0.02)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -18,7 +18,7 @@ from chainlib.eth.block import (
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageDefault
|
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from chainlib.eth.tx import receipt
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageDefault
|
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from hexathon import (
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageDefault
|
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from hexathon import (
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageUnit
|
from erc20_demurrage_token.unittest.base import TestDemurrageUnit
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import eth_tester
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageDefault
|
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from hexathon import (
|
|||||||
from erc20_demurrage_token import DemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
# test imports
|
# test imports
|
||||||
from tests.base import TestDemurrageSingle
|
from erc20_demurrage_token.unittest.base import TestDemurrageSingle
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ contract DemurrageTokenMultiCap {
|
|||||||
// (this constant x 1000000 is contained within 128 bits)
|
// (this constant x 1000000 is contained within 128 bits)
|
||||||
uint256 constant ppmDivider = 100000000000000000000000000000000;
|
uint256 constant ppmDivider = 100000000000000000000000000000000;
|
||||||
|
|
||||||
|
// demurrage decimal width; 38 places
|
||||||
|
uint256 public immutable resolutionFactor = ppmDivider * 1000000;
|
||||||
|
|
||||||
// Timestamp of start of periods (time which contract constructor was called)
|
// Timestamp of start of periods (time which contract constructor was called)
|
||||||
uint256 public immutable periodStart;
|
uint256 public immutable periodStart;
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ contract DemurrageTokenMultiNocap {
|
|||||||
// (this constant x 1000000 is contained within 128 bits)
|
// (this constant x 1000000 is contained within 128 bits)
|
||||||
uint256 constant ppmDivider = 100000000000000000000000000000000;
|
uint256 constant ppmDivider = 100000000000000000000000000000000;
|
||||||
|
|
||||||
|
// demurrage decimal width; 38 places
|
||||||
|
uint256 public immutable resolutionFactor = ppmDivider * 1000000;
|
||||||
|
|
||||||
// Timestamp of start of periods (time which contract constructor was called)
|
// Timestamp of start of periods (time which contract constructor was called)
|
||||||
uint256 public immutable periodStart;
|
uint256 public immutable periodStart;
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ contract DemurrageTokenSingleCap {
|
|||||||
uint256 constant growthResolutionFactor = 1000000000000;
|
uint256 constant growthResolutionFactor = 1000000000000;
|
||||||
|
|
||||||
// demurrage decimal width; 38 places
|
// demurrage decimal width; 38 places
|
||||||
uint256 immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
uint256 public immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
||||||
|
|
||||||
// Timestamp of start of periods (time which contract constructor was called)
|
// Timestamp of start of periods (time which contract constructor was called)
|
||||||
uint256 public immutable periodStart;
|
uint256 public immutable periodStart;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ contract DemurrageTokenSingleCap {
|
|||||||
uint256 constant growthResolutionFactor = 1000000000000;
|
uint256 constant growthResolutionFactor = 1000000000000;
|
||||||
|
|
||||||
// demurrage decimal width; 38 places
|
// demurrage decimal width; 38 places
|
||||||
uint256 immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
uint256 public immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
||||||
|
|
||||||
// Timestamp of start of periods (time which contract constructor was called)
|
// Timestamp of start of periods (time which contract constructor was called)
|
||||||
uint256 public immutable periodStart;
|
uint256 public immutable periodStart;
|
||||||
|
|||||||
Reference in New Issue
Block a user