17 Commits

Author SHA1 Message Date
lash
7b6a88c889 Move bitfields to struct 2023-02-08 08:26:15 +00:00
lash
00f4ddba68 Remove growby method 2023-02-08 06:03:56 +00:00
lash
9d7b87be9c Finish repairing cli tools 2023-01-11 13:35:24 +00:00
lash
697d2bf227 Rehabilitate cli tools 2023-01-11 13:06:09 +00:00
lash
e49493def1 Decrease allowance on transferfrom 2023-01-07 07:00:31 +00:00
lash
4f1bd36c12 Add delta allowance, approve sets explicit value 2023-01-05 13:40:23 +00:00
lash
166302ef49 Bump contract version 2022-12-16 12:01:23 +00:00
lash
2ec72bbc55 Add comments, remove dead code, remove make install for dead contracts 2022-12-15 10:48:40 +00:00
lash
97415dbed2 Add simple demurrage check, asserts for longer period checks 2022-12-15 07:25:04 +00:00
lash
3b87961d13 WIP investigate apparent overflow problem 2022-12-13 13:26:27 +00:00
lash
a56f1c4869 WIP test burn over time 2022-12-13 13:10:33 +00:00
lash
ef294eaec2 Remove potentially boundless iteration in changePeriod in contract 2022-12-11 09:41:56 +00:00
lash
2f4f8bb7b9 Add test for arbitrary account demurrage honoring burn amount 2022-12-05 18:53:07 +00:00
lash
140dde7bc3 Add total burned method 2022-12-05 18:25:04 +00:00
lash
90fb95208f Add test for redistribution after burn 2022-12-05 18:17:58 +00:00
99a9915d11 Merge branch 'dev-0.2.0' into 'master'
Runaway redistribution

See merge request cicnet/erc20-demurrage-token!10
2022-05-30 07:53:22 +00:00
Louis Holbrook
20e3a783fd Runaway redistribution 2022-05-30 07:53:22 +00:00
29 changed files with 1309 additions and 305 deletions

5
CAVEAT Normal file
View 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
View 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
VERSION Normal file
View File

@@ -0,0 +1 @@
0.1.2

View File

@@ -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
* Apply demurrage cli tool
- 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

View File

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

View File

@@ -11,24 +11,34 @@ import os
import json
import argparse
import logging
import datetime
import math
# external imports
import confini
from funga.eth.signer import EIP155Signer
from funga.eth.keystore.dict import DictKeystore
from chainlib.chain import ChainSpec
from chainlib.eth.nonce import (
RPCNonceOracle,
OverrideNonceOracle,
)
from chainlib.eth.gas import (
RPCGasOracle,
OverrideGasOracle,
import chainlib.eth.cli
from chainlib.eth.block import (
block_latest,
block_by_number,
Block,
)
from chainlib.eth.connection import EthHTTPConnection
from chainlib.eth.tx import receipt
from chainlib.eth.constant import ZERO_ADDRESS
from hexathon import to_int as hex_to_int
import chainlib.eth.cli
from chainlib.eth.settings import process_settings
from chainlib.settings import ChainSettings
from chainlib.eth.cli.arg import (
Arg,
ArgFlag,
process_args,
)
from chainlib.eth.cli.config import (
Config,
process_config,
)
from chainlib.eth.cli.log import process_log
# local imports
import erc20_demurrage_token
@@ -37,53 +47,105 @@ from erc20_demurrage_token import (
DemurrageTokenSettings,
)
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
script_dir = os.path.dirname(__file__)
data_dir = os.path.join(script_dir, '..', 'data')
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()
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()
wallet.from_config(config)
logg = process_log(args, logg)
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
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():
signer = rpc.get_signer()
signer_address = rpc.get_sender_address()
chain_spec = settings.get('CHAIN_SPEC')
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()
nonce_oracle = rpc.get_nonce_oracle()
o = block_by_number(block_start_number)
r = conn.do(o)
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)
if config.get('_RPC_SEND'):
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']
block_start = Block(r)
block_start_timestamp = block_start.timestamp
block_start_datetime = datetime.datetime.fromtimestamp(block_start_timestamp)
print(address)
else:
gas_oracle = settings.get('FEE_ORACLE')
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)
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__':

View File

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

View File

@@ -38,6 +38,15 @@ class DemurrageTokenSettings:
self.sink_address = None
def __str__(self):
return 'name {} demurrage level {} period minutes {} sink address {}'.format(
self.name,
self.demurrage_level,
self.period_minutes,
self.sink_address,
)
class DemurrageToken(ERC20):
__abi = {}
@@ -108,6 +117,34 @@ class DemurrageToken(ERC20):
return DemurrageToken.__bytecode[name]
def increase_allowance(self, contract_address, sender_address, address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('increaseAllowance')
enc.typ(ABIContractType.ADDRESS)
enc.typ(ABIContractType.UINT256)
enc.address(address)
enc.uint256(value)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def decrease_allowance(self, contract_address, sender_address, address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('decreaseAllowance')
enc.typ(ABIContractType.ADDRESS)
enc.typ(ABIContractType.UINT256)
enc.address(address)
enc.uint256(value)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def add_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('addMinter')
@@ -146,6 +183,33 @@ class DemurrageToken(ERC20):
return tx
def burn(self, contract_address, sender_address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('burn')
enc.typ(ABIContractType.UINT256)
enc.uint256(value)
data = enc.get()
tx = self.template(sender_address, contract_address, use_nonce=True)
tx = self.set_code(tx, data)
tx = self.finalize(tx, tx_format)
return tx
def total_burned(self, contract_address, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('totalBurned')
data = add_0x(enc.get())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
o['params'].append(self.normalize(tx))
o['params'].append('latest')
o = j.finalize(o)
return o
def to_base_amount(self, contract_address, value, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
@@ -246,8 +310,11 @@ class DemurrageToken(ERC20):
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionPeriod')
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
v = strip_0x(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())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
@@ -257,22 +324,26 @@ class DemurrageToken(ERC20):
return o
def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionParticipants')
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
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_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
# j = JSONRPCRequest(id_generator)
# o = j.template()
# o['method'] = 'eth_call'
# enc = ABIContractEncoder()
# enc.method('toRedistributionParticipants')
# v = strip_0x(redistribution)
# enc.typ_literal('(uint32,uint72,uint104)')
# #enc.typ(ABIContractType.BYTES32)
# enc.bytes32(v[:64])
# enc.bytes32(v[64:128])
# enc.bytes32(v[128:192])
# 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_redistribution_supply(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
@@ -280,8 +351,11 @@ class DemurrageToken(ERC20):
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionSupply')
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
v = strip_0x(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())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
@@ -297,8 +371,11 @@ class DemurrageToken(ERC20):
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionDemurrageModifier')
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
v = strip_0x(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())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
@@ -380,28 +457,32 @@ class DemurrageToken(ERC20):
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):
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):
j = JSONRPCRequest(id_generator)
o = j.template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('growBy')
enc.typ(ABIContractType.UINT256)
enc.typ(ABIContractType.UINT256)
enc.uint256(value)
enc.uint256(period)
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 grow_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
# j = JSONRPCRequest(id_generator)
# o = j.template()
# o['method'] = 'eth_call'
# enc = ABIContractEncoder()
# enc.method('growBy')
# enc.typ(ABIContractType.UINT256)
# enc.typ(ABIContractType.UINT256)
# enc.uint256(value)
# enc.uint256(period)
# 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 decay_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
j = JSONRPCRequest(id_generator)
@@ -447,8 +528,11 @@ class DemurrageToken(ERC20):
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('getDistributionFromRedistribution')
enc.typ(ABIContractType.BYTES32)
enc.bytes32(redistribution)
v = strip_0x(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())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
@@ -513,6 +597,7 @@ class DemurrageToken(ERC20):
def parse_supply_cap(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_grow_by(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@@ -536,3 +621,8 @@ class DemurrageToken(ERC20):
@classmethod
def parse_resolution_factor(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_total_burned(self, v):
return abi_decode_single(ABIContractType.UINT256, v)

View File

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

View File

@@ -1,3 +1,3 @@
chainlib-eth~=0.0.27
eth-erc20~=0.1.10
funga-eth~=0.5.6
chainlib-eth~=0.4.11
eth-erc20~=0.5.1
funga-eth~=0.6.0

View File

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

View File

@@ -1,6 +1,6 @@
[metadata]
name = erc20-demurrage-token
version = 0.0.11
version = 0.2.0
description = ERC20 token with redistributed continual demurrage
author = Louis Holbrook
author_email = dev@holbrook.no
@@ -25,12 +25,13 @@ licence_files =
[options]
include_package_data = True
python_requires = >= 3.6
python_requires = >= 3.7
packages =
erc20_demurrage_token
erc20_demurrage_token.runnable
erc20_demurrage_token.data
erc20_demurrage_token.sim
erc20_demurrage_token.unittest
[options.package_data]
* =

View File

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

320
python/tests/test_burn.py Normal file
View 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()

View 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()

View File

@@ -28,23 +28,23 @@ testdir = os.path.dirname(__file__)
class TestGrowth(TestDemurrageDefault):
def test_grow_by(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
growth_factor = (1000000 + self.tax_level) / 1000000
v = 1000000000
o = c.grow_by(self.address, v, 1, sender_address=self.accounts[0])
r = self.rpc.do(o)
g = c.parse_grow_by(r)
self.assertEqual(int(v * growth_factor), g)
period = 10
growth_factor = (1 + (self.tax_level) / 1000000) ** period
o = c.grow_by(self.address, v, period, sender_address=self.accounts[0])
r = self.rpc.do(o)
g = c.parse_grow_by(r)
self.assertEqual(int(v * growth_factor), g)
# def test_grow_by(self):
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
#
# growth_factor = (1000000 + self.tax_level) / 1000000
# v = 1000000000
# o = c.grow_by(self.address, v, 1, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# g = c.parse_grow_by(r)
# self.assertEqual(int(v * growth_factor), g)
#
# period = 10
# growth_factor = (1 + (self.tax_level) / 1000000) ** period
# o = c.grow_by(self.address, v, period, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# g = c.parse_grow_by(r)
# self.assertEqual(int(v * growth_factor), g)
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])
r = self.rpc.do(o)
g = c.parse_decay_by(r)
self.assertEqual(int(v * growth_factor), g)
self.assertEqual(int(v * growth_factor), g)
if __name__ == '__main__':

View File

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

View File

@@ -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()

View File

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

View File

@@ -1,18 +1,24 @@
pragma solidity > 0.6.11;
pragma solidity >= 0.8.0;
// SPDX-License-Identifier: GPL-3.0-or-later
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
// (Uses sub-byte boundaries)
bytes32[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
uint8 constant shiftRedistributionPeriod = 0;
uint256 constant maskRedistributionPeriod = 0x00000000000000000000000000000000000000000000000000000000ffffffff; // (1 << 32) - 1
uint8 constant shiftRedistributionValue = 32;
uint256 constant maskRedistributionValue = 0x00000000000000000000000000000000000000ffffffffffffffffff00000000; // ((1 << 72) - 1) << 32
uint8 constant shiftRedistributionDemurrage = 104;
uint256 constant maskRedistributionDemurrage = 0x0000000000ffffffffffffffffffffffffffff00000000000000000000000000; // ((1 << 20) - 1) << 140
// bytes32[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
// uint8 constant shiftRedistributionPeriod = 0;
// uint256 constant maskRedistributionPeriod = 0x00000000000000000000000000000000000000000000000000000000ffffffff; // (1 << 32) - 1
// uint8 constant shiftRedistributionValue = 32;
// uint256 constant maskRedistributionValue = 0x00000000000000000000000000000000000000ffffffffffffffffff00000000; // ((1 << 72) - 1) << 32
// uint8 constant shiftRedistributionDemurrage = 104;
// uint256 constant maskRedistributionDemurrage = 0x0000000000ffffffffffffffffffffffffffff00000000000000000000000000; // ((1 << 36) - 1) << 140
// Account balances
mapping (address => uint256) account;
@@ -20,8 +26,6 @@ contract DemurrageTokenSingleCap {
// Cached demurrage amount, ppm with 38 digit resolution
uint128 public demurrageAmount;
// Cached demurrage period; the period for which demurrageAmount was calculated
//uint128 public demurragePeriod;
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
uint256 public demurrageTimestamp;
@@ -40,10 +44,17 @@ contract DemurrageTokenSingleCap {
uint256 public decimals;
// 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
uint256 public minimumParticipantSpend;
// Last executed period
uint256 public lastPeriod;
// Last sink redistribution amount
uint256 public totalSink;
// Value of burnt tokens (burnt tokens do not decay)
uint256 public burned;
// 128 bit resolution of the demurrage divisor
// (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)
// Address to send unallocated redistribution tokens
address sinkAddress;
address public sinkAddress;
// Implements ERC20
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
event Debug(bytes32 _foo);
// Emitted when tokens are burned
event Burn(address indexed _burner, uint256 _value);
// EIP173
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint128 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress) public {
redistributionItem memory initialRedistribution;
// ACL setup
owner = msg.sender;
minter[owner] = true;
@@ -111,17 +127,13 @@ contract DemurrageTokenSingleCap {
demurrageTimestamp = block.timestamp;
periodStart = demurrageTimestamp;
periodDuration = _periodMinutes * 60;
//demurrageAmount = 100000000000000000000000000000000000000 - _taxLevelMinute; // Represents 38 decimal places, same as resolutionFactor
//demurrageAmount = 100000000000000000000000000000000000000;
demurrageAmount = 10000000000000000000000000000;
//demurragePeriod = 1;
demurrageAmount = uint128(nanoDivider) * 100;
taxLevel = _taxLevelMinute; // Represents 38 decimal places
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
redistributions.push(initialRedistribution);
// Misc settings
sinkAddress = _defaultSinkAddress;
minimumParticipantSpend = 10 ** uint256(_decimals);
}
@@ -153,10 +165,8 @@ contract DemurrageTokenSingleCap {
baseBalance = baseBalanceOf(_account);
//periodCount = actualPeriod() - demurragePeriod;
periodCount = getMinutesDelta(demurrageTimestamp);
//currentDemurragedAmount = uint128(decayBy(demurrageAmount, periodCount));
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
@@ -211,7 +221,7 @@ contract DemurrageTokenSingleCap {
changePeriod();
baseAmount = toBaseAmount(_amount);
totalSupply += _amount;
supply += _amount;
increaseBaseBalance(_beneficiary, baseAmount);
emit Mint(msg.sender, _beneficiary, _amount);
saveRedistributionSupply();
@@ -220,47 +230,82 @@ contract DemurrageTokenSingleCap {
// Deserializes the redistribution word
// uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) public pure returns(bytes32) {
bytes32 redistribution;
// function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) public pure returns(bytes32) {
// bytes32 redistribution;
//
// redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
// redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
// redistribution |= bytes32(_period & maskRedistributionPeriod);
// return redistribution;
// }
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
redistribution |= bytes32(_period & maskRedistributionPeriod);
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) public pure returns(redistributionItem memory) {
redistributionItem memory redistribution;
redistribution.period = uint32(_period);
redistribution.value = uint72(_value);
redistribution.demurrage = uint104(_demurrageModifierPpm);
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
function toRedistributionPeriod(bytes32 redistribution) public pure returns (uint256) {
return uint256(redistribution) & maskRedistributionPeriod;
// // Serializes the supply part of the redistribution word
// function toRedistributionSupply(bytes32 redistribution) public pure returns (uint256) {
// 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) {
return (uint256(redistribution) & maskRedistributionValue) >> shiftRedistributionValue;
//
// // Serializes the number of participants part of the redistribution word
// 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
function redistributionCount() public view returns (uint256) {
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) {
uint256 currentRedistribution;
redistributionItem memory currentRedistribution;
uint256 grownSupply;
//grownSupply = growBy(totalSupply, 1);
grownSupply = totalSupply;
currentRedistribution = uint256(redistributions[redistributions.length-1]);
currentRedistribution &= (~maskRedistributionValue);
currentRedistribution |= (grownSupply << shiftRedistributionValue);
grownSupply = totalSupply();
currentRedistribution = redistributions[redistributions.length-1];
currentRedistribution.value = uint72(grownSupply);
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
redistributions[redistributions.length-1] = currentRedistribution;
return true;
}
@@ -269,15 +314,16 @@ contract DemurrageTokenSingleCap {
return uint128((block.timestamp - periodStart) / periodDuration + 1);
}
// Add an entered demurrage period to the redistribution array
function checkPeriod() private view returns (bytes32) {
bytes32 lastRedistribution;
// Retrieve next redistribution if the period threshold has been crossed
function checkPeriod() private view returns (redistributionItem memory) {
redistributionItem memory lastRedistribution;
redistributionItem memory emptyRedistribution;
uint256 currentPeriod;
lastRedistribution = redistributions[redistributions.length-1];
lastRedistribution = redistributions[lastPeriod];
currentPeriod = this.actualPeriod();
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
return bytes32(0x00);
return emptyRedistribution;
}
return lastRedistribution;
}
@@ -285,12 +331,11 @@ contract DemurrageTokenSingleCap {
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
uint256 difference;
//difference = _supply * (resolutionFactor - _demurrageAmount); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000)); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000));
return difference / resolutionFactor;
}
function getDistributionFromRedistribution(bytes32 _redistribution) public returns (uint256) {
function getDistributionFromRedistribution(redistributionItem memory _redistribution) public returns (uint256) {
uint256 redistributionSupply;
uint256 redistributionDemurrage;
@@ -300,11 +345,15 @@ contract DemurrageTokenSingleCap {
}
// 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 baseUnit;
unit = getDistributionFromRedistribution(_redistribution);
increaseBaseBalance(sinkAddress, toBaseAmount(unit));
baseUnit = toBaseAmount(unit) - totalSink;
increaseBaseBalance(sinkAddress, baseUnit);
lastPeriod += 1;
totalSink += baseUnit;
return unit;
}
@@ -319,13 +368,9 @@ contract DemurrageTokenSingleCap {
}
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
//uint128 epochPeriodCount;
uint256 periodCount;
uint256 lastDemurrageAmount;
//epochPeriodCount = actualPeriod();
//periodCount = epochPeriodCount - demurragePeriod;
periodCount = getMinutesDelta(demurrageTimestamp);
if (periodCount == 0) {
return false;
@@ -355,37 +400,46 @@ contract DemurrageTokenSingleCap {
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
// Note that the supply for the consecutive period will be taken at the time of code execution, and thus not necessarily at the time when the redistribution period threshold was crossed.
function changePeriod() public returns (bool) {
bytes32 currentRedistribution;
bytes32 nextRedistribution;
redistributionItem memory currentRedistribution;
redistributionItem memory nextRedistribution;
redistributionItem memory lastRedistribution;
uint256 currentPeriod;
uint256 currentDemurrageAmount;
uint256 lastDemurrageAmount;
uint256 nextRedistributionDemurrage;
uint256 demurrageCounts;
uint256 periodTimestamp;
uint256 nextPeriod;
applyDemurrage();
currentRedistribution = checkPeriod();
if (currentRedistribution == bytes32(0x00)) {
if (isEmptyRedistribution(currentRedistribution)) {
return false;
}
// calculate the decay from previous redistributino
lastRedistribution = redistributions[lastPeriod];
currentPeriod = toRedistributionPeriod(currentRedistribution);
nextPeriod = currentPeriod + 1;
periodTimestamp = getPeriodTimeDelta(currentPeriod);
currentDemurrageAmount = demurrageAmount;
demurrageCounts = demurrageCycles(periodTimestamp);
if (demurrageCounts > 0) {
nextRedistributionDemurrage = growBy(currentDemurrageAmount, demurrageCounts);
} else {
nextRedistributionDemurrage = currentDemurrageAmount;
}
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, nextPeriod);
lastDemurrageAmount = toRedistributionDemurrageModifier(lastRedistribution);
demurrageCounts = periodDuration / 60;
nextRedistributionDemurrage = decayBy(lastDemurrageAmount, demurrageCounts);
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply(), nextPeriod);
redistributions.push(nextRedistribution);
applyDefaultRedistribution(nextRedistribution);
@@ -394,18 +448,18 @@ contract DemurrageTokenSingleCap {
}
// Reverse a value reduced by demurrage by the given period to its original value
function growBy(uint256 _value, uint256 _period) public view returns (uint256) {
uint256 valueFactor;
uint256 truncatedTaxLevel;
valueFactor = growthResolutionFactor;
truncatedTaxLevel = taxLevel / nanoDivider;
for (uint256 i = 0; i < _period; i++) {
valueFactor = valueFactor + ((valueFactor * truncatedTaxLevel) / growthResolutionFactor);
}
return (valueFactor * _value) / growthResolutionFactor;
}
// function growBy(uint256 _value, uint256 _period) public view returns (uint256) {
// uint256 valueFactor;
// uint256 truncatedTaxLevel;
//
// valueFactor = growthResolutionFactor;
// truncatedTaxLevel = taxLevel / nanoDivider;
//
// for (uint256 i = 0; i < _period; i++) {
// valueFactor = valueFactor + ((valueFactor * truncatedTaxLevel) / growthResolutionFactor);
// }
// return (valueFactor * _value) / growthResolutionFactor;
// }
// Calculate a value reduced by demurrage by the given period
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
function toBaseAmount(uint256 _value) public view returns (uint256) {
//return (_value * resolutionFactor) / demurrageAmount;
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
}
@@ -431,14 +484,45 @@ contract DemurrageTokenSingleCap {
function approve(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
if (allowance[msg.sender][_spender] > 0) {
require(_value == 0, 'ZERO_FIRST');
}
changePeriod();
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] += baseValue;
allowance[msg.sender][_spender] = baseValue;
emit Approval(msg.sender, _spender, _value);
return true;
}
// Reduce allowance by amount
function decreaseAllowance(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
baseValue = toBaseAmount(_value);
require(allowance[msg.sender][_spender] >= baseValue);
changePeriod();
allowance[msg.sender][_spender] -= baseValue;
emit Approval(msg.sender, _spender, allowance[msg.sender][_spender]);
return true;
}
// Increase allowance by amount
function increaseAllowance(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
changePeriod();
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] += baseValue;
emit Approval(msg.sender, _spender, allowance[msg.sender][_spender]);
return true;
}
// Implements ERC20, triggers tax and/or redistribution
function transfer(address _to, uint256 _value) public returns (bool) {
uint256 baseValue;
@@ -462,7 +546,9 @@ contract DemurrageTokenSingleCap {
baseValue = toBaseAmount(_value);
require(allowance[_from][msg.sender] >= baseValue);
allowance[_from][msg.sender] -= baseValue;
result = transferBase(_from, _to, baseValue);
emit Transfer(_from, _to, _value);
return result;
}
@@ -474,7 +560,6 @@ contract DemurrageTokenSingleCap {
decreaseBaseBalance(_from, _value);
increaseBaseBalance(_to, _value);
//period = actualPeriod();
return true;
}
@@ -495,6 +580,29 @@ contract DemurrageTokenSingleCap {
emit OwnershipTransferred(oldOwner, owner);
}
// Explicitly and irretrievably burn tokens
// Only token minters can burn tokens
function burn(uint256 _value) public {
require(minter[msg.sender]);
require(_value <= account[msg.sender]);
uint256 _delta = toBaseAmount(_value);
applyDemurrage();
decreaseBaseBalance(msg.sender, _delta);
burned += _value;
emit Burn(msg.sender, _value);
}
// Implements ERC20
function totalSupply() public view returns (uint256) {
return supply - burned;
}
// Return total number of burned tokens
function totalBurned() public view returns (uint256) {
return burned;
}
// Implements EIP165
function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20

View File

@@ -32,7 +32,15 @@ test: all
python ../python/tests/test_redistribution.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/
.PHONY: test install