Compare commits

...
This repository has been archived on 2023-03-01. You can view files and clone it, but cannot push or open issues or pull requests.

8 Commits

Author SHA1 Message Date
Mohamed Sohail 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
William Luke 226f81fc5c Merge branch 'lash/apply-cli' into 'master'
Add apply demurrage cli tool

See merge request cicnet/erc20-demurrage-token!9
2022-03-14 12:58:45 +00:00
Louis Holbrook 370efb3192 Add apply demurrage cli tool 2022-03-14 12:58:45 +00:00
Louis Holbrook 47ee1cfa45 Merge branch 'lash/gas-safety-valve' into 'master'
bug: Wrong redistribution amount + limited demurrage apply

Closes grassrootseconomics/cic-internal-integration#276, grassrootseconomics/cic-internal-integration#273, and grassrootseconomics/cic-internal-integration#272

See merge request cicnet/erc20-demurrage-token!7
2022-03-02 09:01:50 +00:00
Louis Holbrook db56e0d33f bug: Wrong redistribution amount + limited demurrage apply 2022-03-02 09:01:50 +00:00
Louis Holbrook d0c02eadbf Merge branch 'lum/add-ci' into 'master'
ci: add basic ci

See merge request cicnet/erc20-demurrage-token!8
2022-03-02 08:54:20 +00:00
William Luke ed60b5923b ci: add basic ci 2022-03-02 08:54:20 +00:00
23 changed files with 629 additions and 63 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ __pycache__
gmon.out
solidity/*.json
solidity/*.bin
.venv
venv

36
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,36 @@
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
# Official language image. Look for the different tagged releases at:
# https://hub.docker.com/r/library/python/tags/
image: python:3.8
# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
paths:
- .cache/pip
- venv/
before_script:
- cd ./python
- python --version # For debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
test:
script:
- pip install -r requirements.txt -r test_requirements.txt --extra-index-url https://pip.grassrootseconomics.net
- bash run_tests.sh

View File

@ -1,4 +1,14 @@
- 0.0.2-pending
- 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
* Settable sink address
- 0.0.9
* Correct redistribution amount for SingleNocap contract
- 0.0.2
* Move to chainlib-eth
- 0.0.1-unreleased
- 0.0.1
* Interface for redistributed and non-redistributed, with or without cap

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

@ -0,0 +1,144 @@
"""Deploy sarafu token
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
"""
# standard imports
import sys
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,
)
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
import chainlib.eth.cli
from hexathon import to_int as hex_to_int
# local imports
import erc20_demurrage_token
from erc20_demurrage_token import (
DemurrageToken,
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')
arg_flags = chainlib.eth.cli.argflag_std_write | chainlib.eth.cli.Flag.EXEC
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('--steps', type=int, default=0, help='Max demurrage steps to apply per round')
args = argparser.parse_args()
config = chainlib.eth.cli.Config.from_args(args, arg_flags, default_fee_limit=DemurrageToken.gas(), base_config_dir=config_dir)
config.add(args.steps, '_STEPS', False)
logg.debug('config loaded:\n{}'.format(config))
wallet = chainlib.eth.cli.Wallet()
wallet.from_config(config)
rpc = chainlib.eth.cli.Rpc(wallet=wallet)
conn = rpc.connect_by_config(config)
chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
def main():
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)
o = block_by_number(block_start_number)
r = conn.do(o)
block_start = Block(r)
block_start_timestamp = block_start.timestamp
block_start_datetime = datetime.datetime.fromtimestamp(block_start_timestamp)
gas_oracle = rpc.get_gas_oracle()
c = DemurrageToken(chain_spec, gas_oracle=gas_oracle)
o = c.demurrage_timestamp(config.get('_EXEC_ADDRESS'))
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 = rpc.get_signer()
signer_address = rpc.get_sender_address()
nonce_oracle = rpc.get_nonce_oracle()
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
(tx_hash_hex, o) = c.apply_demurrage(config.get('_EXEC_ADDRESS'), signer_address, limit=config.get('_STEPS'))
if config.get('_RPC_SEND'):
print(tx_hash_hex)
conn.do(o)
if config.get('_WAIT_ALL') or (i == rounds - 1 and config.get('_WAIT')):
r = conn.wait(tx_hash_hex)
if r['status'] == 0:
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')
sys.exit(1)
else:
print(o)
if __name__ == '__main__':
main()

View File

@ -49,8 +49,8 @@ arg_flags = chainlib.eth.cli.argflag_std_write
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
argparser.add_argument('--name', dest='token_name', type=str, help='Token name')
argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol')
argparser.add_argument('--decimals', dest='token_decimals', default=18, type=int, help='Token decimals')
argparser.add_argument('--sink-address', dest='sink_address', default=ZERO_ADDRESS, type=str, help='demurrage level,ppm per minute')
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')
@ -90,7 +90,6 @@ 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()

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 = {}
@ -325,8 +334,19 @@ class DemurrageToken(ERC20):
return o
def apply_demurrage(self, contract_address, sender_address):
return self.transact_noarg('applyDemurrage', contract_address, sender_address)
def apply_demurrage(self, contract_address, sender_address, limit=0, tx_format=TxFormat.JSONRPC):
if limit == 0:
return self.transact_noarg('applyDemurrage', contract_address, sender_address)
enc = ABIContractEncoder()
enc.method('applyDemurrageLimited')
enc.typ(ABIContractType.UINT256)
enc.uint256(limit)
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 change_period(self, contract_address, sender_address):
@ -369,6 +389,10 @@ 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)

View File

@ -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)
@ -101,7 +102,12 @@ class TestDemurrage(EthTesterCase):
# 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
@ -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.15
eth-erc20~=0.1.5
funga-eth~=0.5.1
chainlib-eth>=0.1.0,<0.2.0
eth-erc20~=0.3.0
funga-eth~=0.6.0

View File

@ -17,7 +17,12 @@ done
modes=(SingleCap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_period.py
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
done
modes=(SingleNocap) # other contracts need to be updted
for m in ${modes[@]}; do
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_unit.py
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution_single.py
done
modes=(MultiCap SingleCap)

View File

@ -1,6 +1,6 @@
[metadata]
name = erc20-demurrage-token
version = 0.0.8
version = 0.1.1
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]
* =
@ -40,3 +41,4 @@ packages =
[options.entry_points]
console_scripts =
erc20-demurrage-token-deploy = erc20_demurrage_token.runnable.deploy:main
erc20-demurrage-token-apply = erc20_demurrage_token.runnable.apply:main

View File

@ -54,6 +54,32 @@ class TestBasic(TestDemurrageDefault):
self.assertEqual(balance, 1024)
def test_apply_demurrage_limited(self):
modifier = (10 ** 28)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage_amount = c.parse_demurrage_amount(r)
self.assertEqual(modifier, demurrage_amount)
self.backend.time_travel(self.start_time + 120)
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0], limit=1)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage_amount = c.parse_demurrage_amount(r)
modifier_base = 1000000 - self.tax_level
modifier = int(modifier_base * (10 ** 22)) # 38 decimal places minus 6 (1000000)
self.assertEqual(modifier, demurrage_amount)
def test_apply_demurrage(self):
modifier = (10 ** 28)
@ -93,7 +119,6 @@ class TestBasic(TestDemurrageDefault):
r = self.rpc.do(o)
demurrage_amount = c.parse_demurrage_amount(r)
modifier_base = 1000000 - self.tax_level
logg.debug('modifier base {}'.format(modifier_base))
modifier = int(modifier_base * (10 ** 22)) # 38 decimal places minus 6 (1000000)
self.assertEqual(modifier, demurrage_amount)
@ -107,10 +132,11 @@ class TestBasic(TestDemurrageDefault):
r = self.rpc.do(o)
demurrage_amount = c.parse_demurrage_amount(r)
modifier_base = ((1000000 - self.tax_level) / 1000000) ** 10
logg.warning('mod base {}'.format(modifier_base))
modifier = int(modifier_base * (10 ** 12))
rounding_tolerance_nano = 4000000 # 0.000004% precision
demurrage_amount_truncate = int(demurrage_amount / (10 ** 26)) # equals 12 decimal places
demurrage_amount_truncate = int(demurrage_amount / (10 ** 16)) # equals 38 decimal places - 14 for the modifier magniture - 2 for percent int calc + 6 for token decimals <- TODO verify this calc
self.assertGreaterEqual(modifier, demurrage_amount_truncate - rounding_tolerance_nano)
self.assertLessEqual(modifier, demurrage_amount_truncate)

View File

@ -7,7 +7,15 @@ 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.tx import (
receipt,
TxFactory,
TxFormat,
)
from chainlib.eth.contract import (
ABIContractEncoder,
ABIContractType,
)
# local imports
from erc20_demurrage_token import DemurrageToken
@ -103,5 +111,90 @@ class TestPeriod(TestDemurrageDefault):
self.assertEqual(modifier, period)
def test_change_sink(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
o = c.balance_of(self.address, ZERO_ADDRESS, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance_of(r)
self.assertEqual(balance, 0)
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 102400000000)
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)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(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.balance_of(self.address, ZERO_ADDRESS, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance_of(r)
self.assertGreater(balance, 0)
old_sink_balance = balance
o = c.balance_of(self.address, self.accounts[3], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance_of(r)
self.assertEqual(balance, 0)
nonce_oracle = RPCNonceOracle(self.accounts[5], self.rpc)
c = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
enc = ABIContractEncoder()
enc.method('setSinkAddress')
enc.typ(ABIContractType.ADDRESS)
enc.address(self.accounts[3])
data = enc.get()
o = c.template(self.accounts[5], self.address, use_nonce=True)
o = c.set_code(o, data)
(tx_hash, o) = c.finalize(o, TxFormat.JSONRPC)
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 = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
enc = ABIContractEncoder()
enc.method('setSinkAddress')
enc.typ(ABIContractType.ADDRESS)
enc.address(self.accounts[3])
data = enc.get()
o = c.template(self.accounts[0], self.address, use_nonce=True)
o = c.set_code(o, data)
(tx_hash, o) = c.finalize(o, TxFormat.JSONRPC)
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 * 2) + 1)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(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.balance_of(self.address, ZERO_ADDRESS, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance_of(r)
self.assertLess(balance, old_sink_balance)
o = c.balance_of(self.address, self.accounts[3], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance_of(r)
self.assertGreater(balance, 0)
if __name__ == '__main__':
unittest.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,185 @@
# 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.DEBUG)
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 * 10))
for i in range(1, 11):
logg.debug('checking period {}'.format(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)
i = 10
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_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)
@ -175,9 +183,6 @@ class TestRedistribution(TestDemurrageUnit):
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
r = self.rpc.do(o)
demurrage = c.parse_to_redistribution_item(r)
# o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
# r = self.rpc.do(o)
# demurrage = c.parse_demurrage_amount(r)
logg.debug('\nrediistribution {}\ndemurrage {}\nsupply {}'.format(redistribution, demurrage, supply))
expected_balance = int(supply * (self.tax_level / 1000000))

View File

@ -128,6 +128,12 @@ contract DemurrageTokenSingleCap {
minimumParticipantSpend = 10 ** uint256(_decimals);
}
// Change sink address for redistribution
function setSinkAddress(address _sinkAddress) public {
require(msg.sender == owner);
sinkAddress = _sinkAddress;
}
// Given address will be allowed to call the mintTo() function
function addMinter(address _minter) public returns (bool) {
require(msg.sender == owner);
@ -311,6 +317,10 @@ contract DemurrageTokenSingleCap {
// Calculate and cache the demurrage value corresponding to the (period of the) time of the method call
function applyDemurrage() public returns (bool) {
return applyDemurrageLimited(0);
}
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
//uint128 epochPeriodCount;
uint256 periodCount;
uint256 lastDemurrageAmount;
@ -323,6 +333,12 @@ contract DemurrageTokenSingleCap {
return false;
}
lastDemurrageAmount = demurrageAmount;
// safety limit for exponential calculation to ensure that we can always
// execute this code no matter how much time passes.
if (_rounds > 0 && _rounds < periodCount) {
periodCount = _rounds;
}
demurrageAmount = uint128(decayBy(lastDemurrageAmount, periodCount));
//demurragePeriod = epochPeriodCount;
demurrageTimestamp = demurrageTimestamp + (periodCount * 60);

View File

@ -20,8 +20,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;
@ -42,8 +40,11 @@ contract DemurrageTokenSingleCap {
// Implements ERC20
uint256 public totalSupply;
// 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;
// 128 bit resolution of the demurrage divisor
// (this constant x 1000000 is contained within 128 bits)
@ -71,7 +72,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);
@ -111,17 +112,20 @@ 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);
redistributions.push(initialRedistribution);
// Misc settings
sinkAddress = _defaultSinkAddress;
minimumParticipantSpend = 10 ** uint256(_decimals);
}
// Change sink address for redistribution
function setSinkAddress(address _sinkAddress) public {
require(msg.sender == owner);
sinkAddress = _sinkAddress;
}
// Given address will be allowed to call the mintTo() function
@ -146,10 +150,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);
@ -247,7 +249,6 @@ contract DemurrageTokenSingleCap {
uint256 currentRedistribution;
uint256 grownSupply;
//grownSupply = growBy(totalSupply, 1);
grownSupply = totalSupply;
currentRedistribution = uint256(redistributions[redistributions.length-1]);
currentRedistribution &= (~maskRedistributionValue);
@ -267,7 +268,7 @@ contract DemurrageTokenSingleCap {
bytes32 lastRedistribution;
uint256 currentPeriod;
lastRedistribution = redistributions[redistributions.length-1];
lastRedistribution = redistributions[lastPeriod];
currentPeriod = this.actualPeriod();
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
return bytes32(0x00);
@ -278,7 +279,7 @@ 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));
return difference / resolutionFactor;
}
@ -294,9 +295,13 @@ contract DemurrageTokenSingleCap {
// Returns the amount sent to the sink address
function applyDefaultRedistribution(bytes32 _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;
}
@ -307,18 +312,25 @@ contract DemurrageTokenSingleCap {
// Calculate and cache the demurrage value corresponding to the (period of the) time of the method call
function applyDemurrage() public returns (bool) {
//uint128 epochPeriodCount;
return applyDemurrageLimited(0);
}
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
uint256 periodCount;
uint256 lastDemurrageAmount;
//epochPeriodCount = actualPeriod();
//periodCount = epochPeriodCount - demurragePeriod;
periodCount = getMinutesDelta(demurrageTimestamp);
if (periodCount == 0) {
return false;
}
lastDemurrageAmount = demurrageAmount;
// safety limit for exponential calculation to ensure that we can always
// execute this code no matter how much time passes.
if (_rounds > 0 && _rounds < periodCount) {
periodCount = _rounds;
}
demurrageAmount = uint128(decayBy(lastDemurrageAmount, periodCount));
//demurragePeriod = epochPeriodCount;
demurrageTimestamp = demurrageTimestamp + (periodCount * 60);
@ -404,7 +416,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);
}
@ -455,7 +466,6 @@ contract DemurrageTokenSingleCap {
decreaseBaseBalance(_from, _value);
increaseBaseBalance(_to, _value);
//period = actualPeriod();
return true;
}