10 Commits

Author SHA1 Message Date
nolash
c69d115965 Introduce sim example 2021-06-06 09:34:18 +02:00
nolash
2f5bb63f9a Use chain string in sim constructor 2021-06-06 06:01:35 +02:00
nolash
4e11f750e8 Revert to mine for every tx, add limit test 2021-06-06 05:57:39 +02:00
nolash
7bdd18664e WIP time travel in sim 2021-06-05 20:23:06 +02:00
nolash
e142dd0432 Add transfer, mint, balance to sim 2021-06-05 19:19:17 +02:00
nolash
64621ca9b3 Add initial sim setup, test 2021-06-05 17:59:34 +02:00
nolash
996c0224cf Rehabilitate deploy script 2021-06-05 14:03:50 +02:00
nolash
b5421cdd4e Remove remainder, particiant count from single mode 2021-06-05 12:50:31 +02:00
nolash
74ef57a6a7 Remove complex account period tracker in single mode 2021-06-05 12:39:53 +02:00
nolash
f338510a1d Remove commented code 2021-06-05 12:05:38 +02:00
25 changed files with 739 additions and 318 deletions

View File

@@ -1 +1 @@
include sarafu_token/data/*
include erc20_demurrage_token/data/*

2
python/config/eth.ini Normal file
View File

@@ -0,0 +1,2 @@
[eth]
provider=http://localhost:8545

View File

@@ -0,0 +1,2 @@
[session]
chain_spec=

8
python/config/token.ini Normal file
View File

@@ -0,0 +1,8 @@
[token]
redistribution_period=10800
demurrage_level=50
supply_limit=0
symbol=RDT
name=Redistributed Demurraged Token
decimals=6
sink_address=

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

@@ -12,7 +12,8 @@ import json
import argparse
import logging
# third-party imports
# external imports
import confini
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from crypto_dev_signer.keystore.dict import DictKeystore
from chainlib.chain import ChainSpec
@@ -29,7 +30,10 @@ from chainlib.eth.tx import receipt
from chainlib.eth.constant import ZERO_ADDRESS
# local imports
from erc20_demurrage_token import DemurrageToken
from erc20_demurrage_token import (
DemurrageToken,
DemurrageTokenSettings,
)
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
@@ -37,7 +41,10 @@ logg = logging.getLogger()
script_dir = os.path.dirname(__file__)
data_dir = os.path.join(script_dir, '..', 'data')
default_config_dir = os.environ.get('CONFINI_DIR', '/usr/local/share/sarafu-token')
argparser = argparse.ArgumentParser()
argparser.add_argument('-c', '--config', dest='c', type=str, default=default_config_dir, help='configuration directory')
argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)')
argparser.add_argument('-w', action='store_true', help='Wait for the last transaction to be confirmed')
argparser.add_argument('-ww', action='store_true', help='Wait for every transaction to be confirmed')
@@ -50,11 +57,12 @@ 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', default=ZERO_ADDRESS, type=str, help='demurrage level,ppm per minute')
argparser.add_argument('--redistribution-period', default=10080, type=int, help='redistribution period, minutes') # default 10080 = week
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('--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('symbol', default='SRF', type=str, help='Token symbol')
argparser.add_argument('demurrage_level', type=int, help='demurrage level, ppm per minute')
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')
args = argparser.parse_args()
if args.vv:
@@ -65,6 +73,32 @@ elif args.v:
block_last = args.w
block_all = args.ww
# process config
config = confini.Config(args.c)
config.process()
args_override = {
'TOKEN_REDISTRIBUTION_PERIOD': getattr(args, 'redistribution_period'),
'TOKEN_DEMURRAGE_LEVEL': getattr(args, 'demurrage_level'),
'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:
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.dict_override(args_override, 'cli args')
if config.get('TOKEN_SUPPLY_LIMIT') == None:
config.add(0, 'TOKEN_SUPPLY_LIMIT', True)
if config.get('TOKEN_REDISTRIBUTION_PERIOD') == None:
config.add(10800, 'TOKEN_REDISTRIBUTION_PERIOD', True)
logg.debug('config loaded:\n{}'.format(config))
passphrase_env = 'ETH_PASSPHRASE'
if args.env_prefix != None:
passphrase_env = args.env_prefix + '_' + passphrase_env
@@ -104,14 +138,19 @@ if token_name == None:
def main():
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')
(tx_hash_hex, o) = c.constructor(
signer_address,
token_name,
args.symbol,
args.decimals,
args.demurrage_level,
args.redistribution_period,
args.sink_address,
settings,
redistribute=settings.period_minutes > 0,
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
)
if dummy:
print(tx_hash_hex)

View File

@@ -0,0 +1,2 @@
from .sim import DemurrageTokenSimulation
from .error import TxLimitException

View File

@@ -0,0 +1,2 @@
class TxLimitException(RuntimeError):
pass

View File

@@ -0,0 +1,257 @@
# standard imports
import logging
# external imports
from chainlib.chain import ChainSpec
from chainlib.eth.unittest.ethtester import create_tester_signer
from chainlib.eth.unittest.base import TestRPCConnection
from chainlib.eth.tx import (
receipt,
Tx,
)
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import (
OverrideGasOracle,
Gas,
)
from chainlib.eth.address import to_checksum_address
from chainlib.eth.block import (
block_latest,
block_by_number,
block_by_hash,
)
from crypto_dev_signer.keystore.dict import DictKeystore
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
from hexathon import (
strip_0x,
add_0x,
)
# local imports
from erc20_demurrage_token import DemurrageToken
from erc20_demurrage_token.sim.error import TxLimitException
logg = logging.getLogger(__name__)
class DemurrageTokenSimulation:
def __init__(self, chain_str, settings, redistribute=True, cap=0, actors=1):
self.chain_spec = ChainSpec.from_chain_str(chain_str)
self.accounts = []
self.keystore = DictKeystore()
self.signer = EIP155Signer(self.keystore)
self.eth_helper = create_tester_signer(self.keystore)
self.eth_backend = self.eth_helper.backend
self.gas_oracle = OverrideGasOracle(limit=100000, price=1)
self.rpc = TestRPCConnection(None, self.eth_helper, self.signer)
for a in self.keystore.list():
self.accounts.append(add_0x(to_checksum_address(a)))
settings.sink_address = self.accounts[0]
self.actors = []
for i in range(actors):
idx = i % 10
address = self.keystore.new()
self.actors.append(address)
self.accounts.append(address)
nonce_oracle = RPCNonceOracle(self.accounts[idx], conn=self.rpc)
c = Gas(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.create(self.accounts[idx], address, 100000 * 1000000)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
if r['status'] != 1:
raise RuntimeError('failed gas transfer to account #{}: {} from {}'.format(i, address, self.accounts[idx]))
logg.debug('added actor account #{}: {}'.format(i, address))
o = block_latest()
r = self.rpc.do(o)
self.last_block = r
self.start_block = self.last_block
o = block_by_number(r)
r = self.rpc.do(o)
self.last_timestamp = r['timestamp']
self.start_timestamp = self.last_timestamp
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.constructor(self.accounts[0], settings, redistribute=redistribute, cap=cap)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
if (r['status'] != 1):
raise RuntimeError('contract deployment failed')
self.address = r['contract_address']
o = c.decimals(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
self.decimals = c.parse_decimals(r)
self.period_seconds = settings.period_minutes * 60
self.last_block += 1
self.last_timestamp += 1
self.period = 1
self.period_txs = []
self.period_tx_limit = self.period_seconds - 1
self.sink_address = settings.sink_address
logg.info('intialized at block {} timestamp {} period {} demurrage level {} sink address {} (first address in keystore)'.format(
self.last_block,
self.last_timestamp,
settings.period_minutes,
settings.demurrage_level,
settings.sink_address,
)
)
self.eth_helper.disable_auto_mine_transactions()
self.caller_contract = DemurrageToken(self.chain_spec)
self.caller_address = self.accounts[0]
def __check_limit(self):
if self.period_tx_limit == len(self.period_txs):
raise TxLimitException('reached period tx limit {}'.format(self.period_tx_limit))
def __check_tx(self, tx_hash):
o = receipt(tx_hash)
rcpt = self.rpc.do(o)
if rcpt['status'] == 0:
raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index']))
logg.debug('tx {} block {} index {} verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
def get_now(self):
o = block_latest()
r = self.rpc.do(o)
o = block_by_number(r, include_tx=False)
r = self.rpc.do(o)
return r['timestamp']
def get_minutes(self):
t = self.get_now()
return int((t - self.start_timestamp) / 60)
def get_start(self):
return self.start_timestamp
def get_period(self):
return self.period
def get_demurrage_modifier(self):
o = self.caller_contract.demurrage_amount(self.address, sender_address=self.caller_address)
r = self.rpc.do(o)
return float(self.caller_contract.parse_demurrage_amount(r) / (10 ** 38))
def from_units(self, v):
return v * (10 ** self.decimals)
def mint(self, recipient, value):
self.__check_limit()
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], recipient, value)
self.rpc.do(o)
self.__next_block()
self.__check_tx(tx_hash)
self.period_txs.append(tx_hash)
logg.info('mint {} tokens to {} - {}'.format(value, recipient, tx_hash))
return tx_hash
def transfer(self, sender, recipient, value):
nonce_oracle = RPCNonceOracle(sender, conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.transfer(self.address, sender, recipient, value)
self.rpc.do(o)
self.__next_block()
self.__check_tx(tx_hash)
self.period_txs.append(tx_hash)
logg.info('transfer {} tokens from {} to {} - {}'.format(value, sender, recipient, tx_hash))
return tx_hash
def balance(self, holder, base=False):
o = None
if base:
o = self.caller_contract.base_balance_of(self.address, holder, sender_address=self.caller_address)
else:
o = self.caller_contract.balance_of(self.address, holder, sender_address=self.caller_address)
r = self.rpc.do(o)
return self.caller_contract.parse_balance_of(r)
def __next_block(self):
hsh = self.eth_helper.mine_block()
o = block_by_hash(hsh)
r = self.rpc.do(o)
for tx_hash in r['transactions']:
o = receipt(tx_hash)
rcpt = self.rpc.do(o)
if rcpt['status'] == 0:
raise RuntimeError('tx {} (block {} index {}) failed'.format(tx_hash, self.last_block, rcpt['transaction_index']))
logg.debug('tx {} (block {} index {}) verified'.format(tx_hash, self.last_block, rcpt['transaction_index']))
logg.debug('now at block {} timestamp {}'.format(r['number'], r['timestamp']))
def next(self):
target_timestamp = self.start_timestamp + (self.period * self.period_seconds)
logg.debug('warping to {}, {} from start'.format(target_timestamp, target_timestamp - self.start_timestamp))
self.last_timestamp = target_timestamp
self.eth_helper.time_travel(self.last_timestamp)
self.__next_block()
o = block_by_number(self.last_block)
r = self.rpc.do(o)
self.last_block = r['number']
block_base = self.last_block
nonce_oracle = RPCNonceOracle(self.accounts[2], conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.change_period(self.address, self.accounts[2])
self.rpc.do(o)
for actor in self.actors:
nonce_oracle = RPCNonceOracle(actor, conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.apply_redistribution_on_account(self.address, actor, actor)
self.rpc.do(o)
nonce_oracle = RPCNonceOracle(self.sink_address, conn=self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=self.gas_oracle)
(tx_hash, o) = c.apply_redistribution_on_account(self.address, self.sink_address, self.sink_address)
self.rpc.do(o)
self.__next_block()
o = block_latest()
self.last_block = self.rpc.do(o)
o = block_by_number(self.last_block)
r = self.rpc.do(o)
for tx_hash in r['transactions']:
o = receipt(tx_hash)
rcpt = self.rpc.do(o)
if rcpt['status'] == 0:
raise RuntimeError('demurrage step failed on block {}'.format(self.last_block))
self.last_timestamp = r['timestamp']
logg.debug('next concludes at block {} timestamp {}, {} after start'.format(self.last_block, self.last_timestamp, self.last_timestamp - self.start_timestamp))
self.period += 1
self.period_txs = []
return (self.last_block, self.last_timestamp)

View File

@@ -84,6 +84,7 @@ class DemurrageToken(ERC20):
name += 'Cap'
else:
name += 'Nocap'
logg.debug('bytecode name {}'.format(name))
return name
@@ -222,6 +223,66 @@ class DemurrageToken(ERC20):
return o
def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS):
o = jsonrpc_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')
return o
def to_redistribution_supply(self, contract_address, redistribution, sender_address=ZERO_ADDRESS):
o = jsonrpc_template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionSupply')
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')
return o
def to_redistribution_demurrage_modifier(self, contract_address, redistribution, sender_address=ZERO_ADDRESS):
o = jsonrpc_template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('toRedistributionDemurrageModifier')
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')
return o
def base_balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS):
o = jsonrpc_template()
o['method'] = 'eth_call'
enc = ABIContractEncoder()
enc.method('baseBalanceOf')
enc.typ(ABIContractType.ADDRESS)
enc.address(address)
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')
return o
def apply_demurrage(self, contract_address, sender_address):
return self.transact_noarg('applyDemurrage', contract_address, sender_address)
@@ -307,6 +368,11 @@ class DemurrageToken(ERC20):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_to_redistribution_item(self, v):
return abi_decode_single(ABIContractType.UINT256, v)
@classmethod
def parse_supply_cap(self, v):
return abi_decode_single(ABIContractType.UINT256, v)

76
python/examples/sim.py Normal file
View File

@@ -0,0 +1,76 @@
# standard imports
import logging
# local imports
from erc20_demurrage_token import DemurrageTokenSettings
from erc20_demurrage_token.sim import DemurrageTokenSimulation
logging.basicConfig(level=logging.WARNING)
logg = logging.getLogger()
decay_per_minute = 0.000050105908373373 # equals approx 2% per month
# parameters for simulation object
settings = DemurrageTokenSettings()
settings.name = 'Simulated Demurrage Token'
settings.symbol = 'SIM'
settings.decimals = 6
settings.demurrage_level = int(decay_per_minute*(10**40))
settings.period_minutes = 10800 # 1 week in minutes
chain = 'evm:foochain:42'
cap = sim.from_units(10 ** 12) # 1 tn token units, with 6 decimal places
# instantiate simulation
sim = DemurrageTokenSimulation(chain, settings, redistribute=True, cap=cap, actors=10)
# name the usual suspects
alice = sim.actors[0]
bob = sim.actors[1]
carol = sim.actors[2]
# mint and transfer (every single action advances one block, and one second in time)
sim.mint(alice, sim.from_units(100)) # 10000000 tokens
sim.mint(bob, sim.from_units(100))
sim.transfer(alice, carol, sim.from_units(50))
# check that balances have been updated
assert sim.balance(alice) == sim.from_units(50)
assert sim.balance(bob) == sim.from_units(100)
assert sim.balance(carol) == sim.from_units(50)
# advance to next redistribution period
sim.next()
# inspect balances
print('alice balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(alice), sim.balance(alice, base=True)))
print('bob balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(bob), sim.balance(bob, base=True)))
print('carol balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(carol), sim.balance(carol, base=True)))
# get times
minutes = sim.get_minutes()
start = sim.get_now()
timestamp = sim.get_start()
period = sim.get_period()
print('start {} now {} period {} minutes passed {}'.format(start, timestamp, period, minutes))
contract_demurrage = 1 - sim.get_demurrage_modifier() # demurrage in percent (float)
frontend_demurrage = ((1 - decay_per_minute) ** minutes / 100) # corresponding demurrage modifier (float)
demurrage_delta = contract_demurrage - frontend_demurrage # difference between demurrage in contract and demurrage calculated in frontend
alice_checksum = 50000000 - (50000000 * frontend_demurrage) + (200000000 * frontend_demurrage) # alice's balance calculated with frontend demurrage
print("""alice frontend balance {}
alice contract balance {}
frontend demurrage {}
contract demurrage {}
demurrage delta {}""".format(
alice_checksum,
sim.balance(alice),
frontend_demurrage,
contract_demurrage,
demurrage_delta),
)
balance_sum = sim.balance(alice) + sim.balance(bob) + sim.balance(carol)
print('sum of contract demurraged balances {}'.format(balance_sum))

View File

@@ -29,6 +29,7 @@ python_requires = >= 3.6
packages =
erc20_demurrage_token
erc20_demurrage_token.runnable
erc20_demurrage_token.data
[options.package_data]
* =

View File

@@ -6,7 +6,6 @@ export PYTHONPATH=.
modes=(MultiNocap MultiCap SingleCap SingleNocap)
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_pure.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_period.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_basic.py
done
@@ -23,6 +22,7 @@ done
modes=(MultiCap MultiNocap)
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_pure.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution.py
done

View File

@@ -163,3 +163,51 @@ class TestDemurrageCap(TestDemurrage):
self.deploy(c, self.mode)
logg.info('deployed with mode {}'.format(self.mode))
class TestDemurrageReal(TestDemurrage):
def setUp(self):
super(TestDemurrage, self).setUp()
self.tax_level = int(0.000050105908373373*(10**40))
self.period = 10800
self.period_seconds = self.period * 60
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
self.settings = DemurrageTokenSettings()
self.settings.name = 'Foo Token'
self.settings.symbol = 'FOO'
self.settings.decimals = 6
self.settings.demurrage_level = self.tax_level
self.settings.period_minutes = 10800
self.settings.sink_address = self.accounts[9]
self.sink_address = self.settings.sink_address
o = block_latest()
self.start_block = self.rpc.do(o)
o = block_by_number(self.start_block, include_tx=False)
r = self.rpc.do(o)
try:
self.start_time = int(r['timestamp'], 16)
except TypeError:
self.start_time = int(r['timestamp'])
self.default_supply = 1000000000000
self.default_supply_cap = int(self.default_supply * 10)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
if self.mode == None:
self.mode = 'MultiNocap'
logg.debug('executing test setup default mode {}'.format(self.mode))
self.deploy(c, self.mode)
logg.info('deployed with mode {}'.format(self.mode))

View File

@@ -0,0 +1,73 @@
# standard imports
import unittest
import logging
# local imports
from erc20_demurrage_token import DemurrageTokenSettings
from erc20_demurrage_token.sim import DemurrageTokenSimulation
logging.basicConfig(level=logging.INFO)
logg = logging.getLogger()
class TestSim(unittest.TestCase):
def setUp(self):
self.cap = 0
settings = DemurrageTokenSettings()
settings.name = 'Simulated Demurrage Token'
settings.symbol = 'SIM'
settings.decimals = 6
settings.demurrage_level = 5010590837337300000000000000000000 # equals approx 2% per month
settings.period_minutes = 10800 # 1 week in minutes
self.sim = DemurrageTokenSimulation('evm:foochain:42', settings, redistribute=True, cap=self.cap, actors=10)
def test_mint(self):
self.sim.mint(self.sim.actors[0], 1024)
self.sim.next()
balance = self.sim.balance(self.sim.actors[0])
self.assertEqual(balance, 1023)
def test_transfer(self):
self.sim.mint(self.sim.actors[0], 1024)
self.sim.transfer(self.sim.actors[0], self.sim.actors[1], 500)
self.sim.next()
balance = self.sim.balance(self.sim.actors[0])
self.assertEqual(balance, 523)
balance = self.sim.balance(self.sim.actors[1])
self.assertEqual(balance, 499)
def test_more_periods(self):
self.sim.mint(self.sim.actors[0], 1024)
self.sim.mint(self.sim.actors[1], 1024)
self.sim.next()
self.sim.mint(self.sim.actors[0], 1024)
self.sim.next()
balance = self.sim.balance(self.sim.actors[0])
self.assertEqual(balance, 2047)
def test_demurrage(self):
self.sim.mint(self.sim.actors[0], self.sim.from_units(100))
self.sim.mint(self.sim.actors[1], self.sim.from_units(100))
self.sim.transfer(self.sim.actors[0], self.sim.actors[2], self.sim.from_units(10))
self.sim.next()
balance = self.sim.balance(self.sim.actors[0])
self.assertEqual(balance, 90005520)
balance = self.sim.balance(self.sim.actors[1])
self.assertEqual(balance, 99995000)
balance = self.sim.balance(self.sim.actors[1], base=True)
self.assertEqual(balance, 100000000)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,35 @@
# standard imports
import unittest
import logging
# local imports
from erc20_demurrage_token import DemurrageTokenSettings
from erc20_demurrage_token.sim import (
DemurrageTokenSimulation,
TxLimitException,
)
logging.basicConfig(level=logging.INFO)
logg = logging.getLogger()
class TestLimit(unittest.TestCase):
def setUp(self):
self.cap = 0
settings = DemurrageTokenSettings()
settings.name = 'Simulated Demurrage Token'
settings.symbol = 'SIM'
settings.decimals = 6
settings.demurrage_level = 1
settings.period_minutes = 1
self.sim = DemurrageTokenSimulation('evm:foochain:42', settings, redistribute=True, cap=self.cap, actors=1)
def test_limit(self):
with self.assertRaises(TxLimitException):
for i in range(60):
self.sim.mint(self.sim.actors[0], i)
if __name__ == '__main__':
unittest.main()

View File

@@ -26,7 +26,6 @@ logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageDefault):
def test_debug_periods(self):
@@ -261,3 +260,5 @@ class TestRedistribution(TestDemurrageDefault):
self.assertEqual(spender_actual_balance, spender_new_decayed_balance)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,84 @@
# standard imports
import os
import unittest
import json
import logging
# external imports
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import receipt
# local imports
from erc20_demurrage_token import DemurrageToken
# test imports
from tests.base import TestDemurrageReal
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestRedistribution(TestDemurrageReal):
def test_simple_example(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], 100000000)
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.accounts[2], 100000000)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[1], self.accounts[3], 50000000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
self.backend.time_travel(self.start_time + self.period_seconds + 1)
(tx_hash, o) = c.change_period(self.address, self.accounts[1])
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])
redistribution = self.rpc.do(o)
logg.debug('redistribution {}'.format(redistribution))
o = c.to_redistribution_period(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
period = c.parse_to_redistribution_item(r)
logg.debug('period {}'.format(period))
o = c.to_redistribution_participants(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
participants = c.parse_to_redistribution_item(r)
logg.debug('participants {}'.format(participants))
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)
logg.debug('supply {}'.format(supply))
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
modifier = c.parse_to_redistribution_item(r)
logg.debug('modifier {}'.format(modifier))
if __name__ == '__main__':
unittest.main()

View File

@@ -246,7 +246,7 @@ contract DemurrageTokenMultiCap {
return (uint256(redistribution) & maskRedistributionParticipants) >> shiftRedistributionParticipants;
}
// Serializes the number of participants part of the redistribution word
// Serializes the demurrage modifier part of the redistribution word
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
}
@@ -417,6 +417,8 @@ contract DemurrageTokenMultiCap {
uint256 periodTimestamp;
uint256 nextPeriod;
applyDemurrage();
currentRedistribution = checkPeriod();
if (currentRedistribution == bytes32(0x00)) {
return false;
@@ -426,7 +428,7 @@ contract DemurrageTokenMultiCap {
nextPeriod = currentPeriod + 1;
periodTimestamp = getPeriodTimeDelta(currentPeriod);
applyDemurrage();
//applyDemurrage();
currentDemurrageAmount = demurrageAmount;
demurrageCounts = demurrageCycles(periodTimestamp);

View File

@@ -2,29 +2,20 @@ pragma solidity > 0.6.11;
// SPDX-License-Identifier: GPL-3.0-or-later
contract DemurrageTokenSingleNocap {
contract DemurrageTokenSingleCap {
// Redistribution bit field, with associated shifts and masks
// (Uses sub-byte boundaries)
bytes32[] public redistributions; // uint1(isFractional) | uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
bytes32[] public redistributions; // uint95(unused) | uint20(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 shiftRedistributionParticipants = 104;
uint256 constant maskRedistributionParticipants = 0x00000000000000000000000000000fffffffff00000000000000000000000000; // ((1 << 36) - 1) << 104
uint8 constant shiftRedistributionDemurrage = 140;
uint256 constant maskRedistributionDemurrage = 0x000000000000000000000000fffff00000000000000000000000000000000000; // ((1 << 20) - 1) << 140
uint8 constant shiftRedistributionIsFractional = 255;
uint256 constant maskRedistributionIsFractional = 0x8000000000000000000000000000000000000000000000000000000000000000; // 1 << 255
// Account bit field, with associated shifts and masks
// Mirrors structure of redistributions for consistency
mapping (address => bytes32) account; // uint152(unused) | uint32(period) | uint72(value)
uint8 constant shiftAccountValue = 0;
uint256 constant maskAccountValue = 0x0000000000000000000000000000000000000000000000ffffffffffffffffff; // (1 << 72) - 1
uint8 constant shiftAccountPeriod = 72;
uint256 constant maskAccountPeriod = 0x00000000000000000000000000000000000000ffffffff000000000000000000; // ((1 << 32) - 1) << 72
// Account balances
mapping (address => uint256) account;
// Cached demurrage amount, ppm with 38 digit resolution
uint128 public demurrageAmount;
@@ -157,7 +148,7 @@ contract DemurrageTokenSingleNocap {
/// Balance unmodified by demurrage
function baseBalanceOf(address _account) public view returns (uint256) {
return uint256(account[_account]) & maskAccountValue;
return account[_account];
}
/// Increases base balance for a single account
@@ -173,11 +164,7 @@ contract DemurrageTokenSingleNocap {
}
oldBalance = baseBalanceOf(_account);
newBalance = oldBalance + _delta;
require(uint160(newBalance) > uint160(oldBalance), 'ERR_WOULDWRAP'); // revert if increase would result in a wrapped value
workAccount &= (~maskAccountValue);
workAccount |= (newBalance & maskAccountValue);
account[_account] = bytes32(workAccount);
account[_account] = oldBalance + _delta;
return true;
}
@@ -195,10 +182,7 @@ contract DemurrageTokenSingleNocap {
oldBalance = baseBalanceOf(_account);
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
newBalance = oldBalance - _delta;
workAccount &= (~maskAccountValue);
workAccount |= (newBalance & maskAccountValue);
account[_account] = bytes32(workAccount);
account[_account] = oldBalance - _delta;
return true;
}
@@ -207,8 +191,8 @@ contract DemurrageTokenSingleNocap {
function mintTo(address _beneficiary, uint256 _amount) external returns (bool) {
uint256 baseAmount;
require(minter[msg.sender]);
require(_amount + totalSupply <= supplyCap);
require(minter[msg.sender], 'ERR_ACCESS');
require(_amount + totalSupply <= supplyCap, 'ERR_CAP');
changePeriod();
baseAmount = _amount;
@@ -220,12 +204,11 @@ contract DemurrageTokenSingleNocap {
}
// Deserializes the redistribution word
// uint1(isFractional) | uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
// uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) private pure returns(bytes32) {
bytes32 redistribution;
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
redistribution |= bytes32((_participants << shiftRedistributionParticipants) & maskRedistributionParticipants);
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
redistribution |= bytes32(_period & maskRedistributionPeriod);
return redistribution;
@@ -241,11 +224,6 @@ contract DemurrageTokenSingleNocap {
return (uint256(redistribution) & maskRedistributionValue) >> shiftRedistributionValue;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionParticipants(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionParticipants) >> shiftRedistributionParticipants;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
@@ -256,23 +234,6 @@ contract DemurrageTokenSingleNocap {
return redistributions.length;
}
// Add number of participants for the current redistribution period by one
function incrementRedistributionParticipants() private returns (bool) {
bytes32 currentRedistribution;
uint256 tmpRedistribution;
uint256 participants;
currentRedistribution = redistributions[redistributions.length-1];
participants = toRedistributionParticipants(currentRedistribution) + 1;
tmpRedistribution = uint256(currentRedistribution);
tmpRedistribution &= (~maskRedistributionParticipants);
tmpRedistribution |= ((participants << shiftRedistributionParticipants) & maskRedistributionParticipants);
redistributions[redistributions.length-1] = bytes32(tmpRedistribution);
return true;
}
// Save the current total supply amount to the current redistribution period
function saveRedistributionSupply() private returns (bool) {
uint256 currentRedistribution;
@@ -303,36 +264,6 @@ contract DemurrageTokenSingleNocap {
return lastRedistribution;
}
// Deserialize the pemurrage period for the given account is participating in
function accountPeriod(address _account) public view returns (uint256) {
return (uint256(account[_account]) & maskAccountPeriod) >> shiftAccountPeriod;
}
// Save the given demurrage period as the currently participation period for the given address
function registerAccountPeriod(address _account, uint256 _period) private returns (bool) {
account[_account] &= bytes32(~maskAccountPeriod);
account[_account] |= bytes32((_period << shiftAccountPeriod) & maskAccountPeriod);
incrementRedistributionParticipants();
return true;
}
// Determine whether the unit number is rounded down, rounded up or evenly divides.
// Returns 0 if evenly distributed, or the remainder as a positive number
// A _numParts value 0 will be interpreted as the value 1
function remainder(uint256 _numParts, uint256 _sumWhole) public pure returns (uint256) {
uint256 unit;
uint256 truncatedResult;
if (_numParts == 0) { // no division by zero please
revert('ERR_NUMPARTS_ZERO');
}
require(_numParts < _sumWhole); // At least you are never LESS than the sum of your parts. Think about that.
unit = _sumWhole / _numParts;
truncatedResult = unit * _numParts;
return _sumWhole - truncatedResult;
}
// Returns the amount sent to the sink address
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
uint256 redistributionSupply;
@@ -347,32 +278,12 @@ contract DemurrageTokenSingleNocap {
if (truncatedResult < redistributionSupply) {
redistributionPeriod = toRedistributionPeriod(_redistribution); // since we reuse period here, can possibly be optimized by passing period instead
redistributions[redistributionPeriod-1] &= bytes32(~maskRedistributionParticipants); // just to be safe, zero out all participant count data, in this case there will be only one
redistributions[redistributionPeriod-1] |= bytes32(maskRedistributionIsFractional | (1 << shiftRedistributionParticipants));
}
increaseBaseBalance(sinkAddress, unit / ppmDivider);
return unit;
}
// sets the remainder bit for the given period and books the remainder to the sink address balance
// returns false if no change was made
function applyRemainderOnPeriod(uint256 _remainder, uint256 _period) private returns (bool) {
uint256 periodSupply;
if (_remainder == 0) {
return false;
}
// TODO: is this needed?
redistributions[_period-1] |= bytes32(maskRedistributionIsFractional);
periodSupply = toRedistributionSupply(redistributions[_period-1]);
increaseBaseBalance(sinkAddress, periodSupply - _remainder);
return true;
}
// Calculate and cache the demurrage value corresponding to the (period of the) time of the method call
function applyDemurrage() public returns (bool) {
uint128 epochPeriodCount;
@@ -407,8 +318,6 @@ contract DemurrageTokenSingleNocap {
bytes32 currentRedistribution;
bytes32 nextRedistribution;
uint256 currentPeriod;
uint256 currentParticipants;
uint256 currentRemainder;
uint256 currentDemurrageAmount;
uint256 nextRedistributionDemurrage;
uint256 demurrageCounts;
@@ -437,13 +346,7 @@ contract DemurrageTokenSingleNocap {
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, nextPeriod);
redistributions.push(nextRedistribution);
//currentParticipants = toRedistributionParticipants(currentRedistribution);
//if (currentParticipants == 0) {
currentRemainder = applyDefaultRedistribution(currentRedistribution);
//} else {
// currentRemainder = remainder(currentParticipants, totalSupply); // we can use totalSupply directly because it will always be the same as the recorded supply on the current redistribution
// applyRemainderOnPeriod(currentRemainder, currentPeriod);
//}
applyDefaultRedistribution(currentRedistribution);
emit Period(nextPeriod);
return true;
}
@@ -477,43 +380,8 @@ contract DemurrageTokenSingleNocap {
return (valueFactor * _value) / 1000000;
}
// If the given account is participating in a period and that period has been crossed
// THEN increase the base value of the account with its share of the value reduction of the period
function applyRedistributionOnAccount(address _account) public returns (bool) {
// bytes32 periodRedistribution;
// uint256 supply;
// uint256 participants;
// uint256 baseValue;
// uint256 value;
uint256 period;
// uint256 demurrage;
//
period = accountPeriod(_account);
if (period == 0 || period >= actualPeriod()) {
return false;
}
// periodRedistribution = redistributions[period-1];
// participants = toRedistributionParticipants(periodRedistribution);
// if (participants == 0) {
// return false;
// }
//
// supply = toRedistributionSupply(periodRedistribution);
// demurrage = toRedistributionDemurrageModifier(periodRedistribution);
// baseValue = ((supply / participants) * (taxLevel / 1000000)) / ppmDivider;
// value = (baseValue * demurrage) / 1000000;
//
// // zero out period for the account
account[_account] &= bytes32(~maskAccountPeriod);
// increaseBaseBalance(_account, value);
//
// emit Redistribution(_account, period, value);
return true;
}
// Inflates the given amount according to the current demurrage modifier
function toBaseAmount(uint256 _value) public view returns (uint256) {
//return (_value * ppmDivider * 1000000) / toDemurrageAmount(demurrageModifier);
return (_value * ppmDivider * 1000000) / demurrageAmount;
}
@@ -522,7 +390,6 @@ contract DemurrageTokenSingleNocap {
uint256 baseValue;
changePeriod();
//applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] += baseValue;
@@ -536,7 +403,6 @@ contract DemurrageTokenSingleNocap {
bool result;
changePeriod();
//applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
result = transferBase(msg.sender, _to, baseValue);
@@ -544,14 +410,12 @@ contract DemurrageTokenSingleNocap {
return result;
}
// Implements ERC20, triggers tax and/or redistribution
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
uint256 baseValue;
bool result;
changePeriod();
//applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
require(allowance[_from][msg.sender] >= baseValue);
@@ -569,9 +433,6 @@ contract DemurrageTokenSingleNocap {
increaseBaseBalance(_to, _value);
period = actualPeriod();
if (_value >= minimumParticipantSpend && accountPeriod(_from) != period && _from != _to) {
registerAccountPeriod(_from, period);
}
return true;
}

View File

@@ -6,25 +6,16 @@ contract DemurrageTokenSingleNocap {
// Redistribution bit field, with associated shifts and masks
// (Uses sub-byte boundaries)
bytes32[] public redistributions; // uint1(isFractional) | uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
bytes32[] public redistributions; // uint95(unused) | uint20(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 shiftRedistributionParticipants = 104;
uint256 constant maskRedistributionParticipants = 0x00000000000000000000000000000fffffffff00000000000000000000000000; // ((1 << 36) - 1) << 104
uint8 constant shiftRedistributionDemurrage = 140;
uint256 constant maskRedistributionDemurrage = 0x000000000000000000000000fffff00000000000000000000000000000000000; // ((1 << 20) - 1) << 140
uint8 constant shiftRedistributionIsFractional = 255;
uint256 constant maskRedistributionIsFractional = 0x8000000000000000000000000000000000000000000000000000000000000000; // 1 << 255
// Account bit field, with associated shifts and masks
// Mirrors structure of redistributions for consistency
mapping (address => bytes32) account; // uint152(unused) | uint32(period) | uint72(value)
uint8 constant shiftAccountValue = 0;
uint256 constant maskAccountValue = 0x0000000000000000000000000000000000000000000000ffffffffffffffffff; // (1 << 72) - 1
uint8 constant shiftAccountPeriod = 72;
uint256 constant maskAccountPeriod = 0x00000000000000000000000000000000000000ffffffff000000000000000000; // ((1 << 32) - 1) << 72
// Account balances
mapping (address => uint256) account;
// Cached demurrage amount, ppm with 38 digit resolution
uint128 public demurrageAmount;
@@ -153,7 +144,7 @@ contract DemurrageTokenSingleNocap {
/// Balance unmodified by demurrage
function baseBalanceOf(address _account) public view returns (uint256) {
return uint256(account[_account]) & maskAccountValue;
return account[_account];
}
/// Increases base balance for a single account
@@ -169,11 +160,7 @@ contract DemurrageTokenSingleNocap {
}
oldBalance = baseBalanceOf(_account);
newBalance = oldBalance + _delta;
require(uint160(newBalance) > uint160(oldBalance), 'ERR_WOULDWRAP'); // revert if increase would result in a wrapped value
workAccount &= (~maskAccountValue);
workAccount |= (newBalance & maskAccountValue);
account[_account] = bytes32(workAccount);
account[_account] = oldBalance + _delta;
return true;
}
@@ -191,10 +178,7 @@ contract DemurrageTokenSingleNocap {
oldBalance = baseBalanceOf(_account);
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
newBalance = oldBalance - _delta;
workAccount &= (~maskAccountValue);
workAccount |= (newBalance & maskAccountValue);
account[_account] = bytes32(workAccount);
account[_account] = oldBalance - _delta;
return true;
}
@@ -215,12 +199,11 @@ contract DemurrageTokenSingleNocap {
}
// Deserializes the redistribution word
// uint1(isFractional) | uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
// uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) private pure returns(bytes32) {
bytes32 redistribution;
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
redistribution |= bytes32((_participants << shiftRedistributionParticipants) & maskRedistributionParticipants);
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
redistribution |= bytes32(_period & maskRedistributionPeriod);
return redistribution;
@@ -236,11 +219,6 @@ contract DemurrageTokenSingleNocap {
return (uint256(redistribution) & maskRedistributionValue) >> shiftRedistributionValue;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionParticipants(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionParticipants) >> shiftRedistributionParticipants;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
@@ -251,23 +229,6 @@ contract DemurrageTokenSingleNocap {
return redistributions.length;
}
// Add number of participants for the current redistribution period by one
function incrementRedistributionParticipants() private returns (bool) {
bytes32 currentRedistribution;
uint256 tmpRedistribution;
uint256 participants;
currentRedistribution = redistributions[redistributions.length-1];
participants = toRedistributionParticipants(currentRedistribution) + 1;
tmpRedistribution = uint256(currentRedistribution);
tmpRedistribution &= (~maskRedistributionParticipants);
tmpRedistribution |= ((participants << shiftRedistributionParticipants) & maskRedistributionParticipants);
redistributions[redistributions.length-1] = bytes32(tmpRedistribution);
return true;
}
// Save the current total supply amount to the current redistribution period
function saveRedistributionSupply() private returns (bool) {
uint256 currentRedistribution;
@@ -298,36 +259,6 @@ contract DemurrageTokenSingleNocap {
return lastRedistribution;
}
// Deserialize the pemurrage period for the given account is participating in
function accountPeriod(address _account) public view returns (uint256) {
return (uint256(account[_account]) & maskAccountPeriod) >> shiftAccountPeriod;
}
// Save the given demurrage period as the currently participation period for the given address
function registerAccountPeriod(address _account, uint256 _period) private returns (bool) {
account[_account] &= bytes32(~maskAccountPeriod);
account[_account] |= bytes32((_period << shiftAccountPeriod) & maskAccountPeriod);
incrementRedistributionParticipants();
return true;
}
// Determine whether the unit number is rounded down, rounded up or evenly divides.
// Returns 0 if evenly distributed, or the remainder as a positive number
// A _numParts value 0 will be interpreted as the value 1
function remainder(uint256 _numParts, uint256 _sumWhole) public pure returns (uint256) {
uint256 unit;
uint256 truncatedResult;
if (_numParts == 0) { // no division by zero please
revert('ERR_NUMPARTS_ZERO');
}
require(_numParts < _sumWhole); // At least you are never LESS than the sum of your parts. Think about that.
unit = _sumWhole / _numParts;
truncatedResult = unit * _numParts;
return _sumWhole - truncatedResult;
}
// Returns the amount sent to the sink address
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
uint256 redistributionSupply;
@@ -342,32 +273,12 @@ contract DemurrageTokenSingleNocap {
if (truncatedResult < redistributionSupply) {
redistributionPeriod = toRedistributionPeriod(_redistribution); // since we reuse period here, can possibly be optimized by passing period instead
redistributions[redistributionPeriod-1] &= bytes32(~maskRedistributionParticipants); // just to be safe, zero out all participant count data, in this case there will be only one
redistributions[redistributionPeriod-1] |= bytes32(maskRedistributionIsFractional | (1 << shiftRedistributionParticipants));
}
increaseBaseBalance(sinkAddress, unit / ppmDivider);
return unit;
}
// sets the remainder bit for the given period and books the remainder to the sink address balance
// returns false if no change was made
function applyRemainderOnPeriod(uint256 _remainder, uint256 _period) private returns (bool) {
uint256 periodSupply;
if (_remainder == 0) {
return false;
}
// TODO: is this needed?
redistributions[_period-1] |= bytes32(maskRedistributionIsFractional);
periodSupply = toRedistributionSupply(redistributions[_period-1]);
increaseBaseBalance(sinkAddress, periodSupply - _remainder);
return true;
}
// Calculate and cache the demurrage value corresponding to the (period of the) time of the method call
function applyDemurrage() public returns (bool) {
uint128 epochPeriodCount;
@@ -402,8 +313,6 @@ contract DemurrageTokenSingleNocap {
bytes32 currentRedistribution;
bytes32 nextRedistribution;
uint256 currentPeriod;
uint256 currentParticipants;
uint256 currentRemainder;
uint256 currentDemurrageAmount;
uint256 nextRedistributionDemurrage;
uint256 demurrageCounts;
@@ -432,13 +341,7 @@ contract DemurrageTokenSingleNocap {
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, nextPeriod);
redistributions.push(nextRedistribution);
//currentParticipants = toRedistributionParticipants(currentRedistribution);
//if (currentParticipants == 0) {
currentRemainder = applyDefaultRedistribution(currentRedistribution);
//} else {
// currentRemainder = remainder(currentParticipants, totalSupply); // we can use totalSupply directly because it will always be the same as the recorded supply on the current redistribution
// applyRemainderOnPeriod(currentRemainder, currentPeriod);
//}
applyDefaultRedistribution(currentRedistribution);
emit Period(nextPeriod);
return true;
}
@@ -472,43 +375,8 @@ contract DemurrageTokenSingleNocap {
return (valueFactor * _value) / 1000000;
}
// If the given account is participating in a period and that period has been crossed
// THEN increase the base value of the account with its share of the value reduction of the period
function applyRedistributionOnAccount(address _account) public returns (bool) {
// bytes32 periodRedistribution;
// uint256 supply;
// uint256 participants;
// uint256 baseValue;
// uint256 value;
uint256 period;
// uint256 demurrage;
//
period = accountPeriod(_account);
if (period == 0 || period >= actualPeriod()) {
return false;
}
// periodRedistribution = redistributions[period-1];
// participants = toRedistributionParticipants(periodRedistribution);
// if (participants == 0) {
// return false;
// }
//
// supply = toRedistributionSupply(periodRedistribution);
// demurrage = toRedistributionDemurrageModifier(periodRedistribution);
// baseValue = ((supply / participants) * (taxLevel / 1000000)) / ppmDivider;
// value = (baseValue * demurrage) / 1000000;
//
// // zero out period for the account
account[_account] &= bytes32(~maskAccountPeriod);
// increaseBaseBalance(_account, value);
//
// emit Redistribution(_account, period, value);
return true;
}
// Inflates the given amount according to the current demurrage modifier
function toBaseAmount(uint256 _value) public view returns (uint256) {
//return (_value * ppmDivider * 1000000) / toDemurrageAmount(demurrageModifier);
return (_value * ppmDivider * 1000000) / demurrageAmount;
}
@@ -517,7 +385,6 @@ contract DemurrageTokenSingleNocap {
uint256 baseValue;
changePeriod();
//applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] += baseValue;
@@ -531,7 +398,6 @@ contract DemurrageTokenSingleNocap {
bool result;
changePeriod();
//applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
result = transferBase(msg.sender, _to, baseValue);
@@ -546,7 +412,6 @@ contract DemurrageTokenSingleNocap {
bool result;
changePeriod();
//applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
require(allowance[_from][msg.sender] >= baseValue);
@@ -564,9 +429,6 @@ contract DemurrageTokenSingleNocap {
increaseBaseBalance(_to, _value);
period = actualPeriod();
if (_value >= minimumParticipantSpend && accountPeriod(_from) != period && _from != _to) {
registerAccountPeriod(_from, period);
}
return true;
}