Compare commits
12 Commits
master
...
lash/clean
Author | SHA1 | Date | |
---|---|---|---|
|
1a168d81f9 | ||
|
2f57da0e8e | ||
|
51c9c94261 | ||
|
e332f76a04 | ||
|
ebef1948aa | ||
|
22cd7cf18c | ||
|
7a9572e978 | ||
|
8f11bdc2cc | ||
|
aab0bc243c | ||
|
364731b220 | ||
|
a8ff826dad | ||
|
3ae75075e4 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,5 +6,3 @@ __pycache__
|
||||
gmon.out
|
||||
solidity/*.json
|
||||
solidity/*.bin
|
||||
.venv
|
||||
venv
|
@ -1,36 +0,0 @@
|
||||
# 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
|
||||
|
@ -1,14 +0,0 @@
|
||||
- 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
|
||||
* Interface for redistributed and non-redistributed, with or without cap
|
@ -1 +1 @@
|
||||
include erc20_demurrage_token/data/* erc20_demurrage_token/data/config/*.ini *requirements.txt
|
||||
include sarafu_token/data/*
|
||||
|
@ -1,4 +0,0 @@
|
||||
from .token import (
|
||||
DemurrageToken,
|
||||
DemurrageTokenSettings,
|
||||
)
|
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
@ -1,2 +0,0 @@
|
||||
[eth]
|
||||
provider=http://localhost:8545
|
@ -1,2 +0,0 @@
|
||||
[session]
|
||||
chain_spec=
|
@ -1,8 +0,0 @@
|
||||
[token]
|
||||
redistribution_period=10800
|
||||
demurrage_level=50
|
||||
supply_limit=0
|
||||
symbol=RDT
|
||||
name=Redistributed Demurraged Token
|
||||
decimals=6
|
||||
sink_address=
|
@ -1,68 +0,0 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import datetime
|
||||
import math
|
||||
|
||||
# eternal imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
|
||||
# local imports
|
||||
from .token import DemurrageToken
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
class DemurrageCalculator:
|
||||
|
||||
def __init__(self, interest_f_minute):
|
||||
|
||||
self.r_min = interest_f_minute
|
||||
self.r_hour = 1 - ((1 -self.r_min) ** 60)
|
||||
self.r_day = 1 - ((1 -self.r_hour) ** 24)
|
||||
#self.r_week = interest_f_day ** 7
|
||||
logg.info('demurrage calculator set with min {:.32f} hour {:.32f} day {:.32f}'.format(self.r_min, self.r_hour, self.r_day))
|
||||
|
||||
|
||||
def amount_since(self, amount, timestamp):
|
||||
delta = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(timestamp)
|
||||
adjusted_amount = amount * ((1 - self.r_day) ** (delta.days))
|
||||
logg.debug('adjusted for {} days {} -> {}'.format(delta.days, amount, adjusted_amount))
|
||||
|
||||
remainder = delta.seconds
|
||||
remainder_hours = math.floor(remainder / (60 * 60))
|
||||
adjusted_delta = adjusted_amount * ((1 - self.r_hour) ** remainder_hours)
|
||||
adjusted_amount -= (adjusted_amount - adjusted_delta)
|
||||
logg.debug('adjusted for {} hours {} -> {} delta {}'.format(remainder_hours, amount, adjusted_amount, adjusted_delta))
|
||||
|
||||
remainder -= (remainder_hours * (60 * 60))
|
||||
remainder_minutes = math.floor(remainder / 60)
|
||||
adjusted_delta = adjusted_amount * ((1 - self.r_min) ** remainder_minutes)
|
||||
adjusted_amount -= (adjusted_amount - adjusted_delta)
|
||||
logg.debug('adjusted for {} minutes {} -> {} delta {}'.format(remainder_minutes, amount, adjusted_amount, adjusted_delta))
|
||||
|
||||
return adjusted_amount
|
||||
|
||||
|
||||
def amount_since_slow(self, amount, timestamp):
|
||||
delta = datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(timestamp)
|
||||
remainder_minutes = math.floor(delta.total_seconds() / 60)
|
||||
adjusted_amount = amount * ((1 - self.r_min) ** remainder_minutes)
|
||||
logg.debug('adjusted for {} minutes {} -> {} delta {}'.format(remainder_minutes, amount, adjusted_amount, amount - adjusted_amount))
|
||||
|
||||
return adjusted_amount
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_contract(rpc, chain_spec, contract_address, sender_address=ZERO_ADDRESS):
|
||||
c = DemurrageToken(chain_spec)
|
||||
o = c.tax_level(contract_address, sender_address=sender_address)
|
||||
r = rpc.do(o)
|
||||
taxlevel_i = c.parse_tax_level(r)
|
||||
|
||||
o = c.resolution_factor(contract_address, sender_address=sender_address)
|
||||
r = rpc.do(o)
|
||||
divider = c.parse_resolution_factor(r)
|
||||
logg.debug('taxlevel {} f {}'.format(taxlevel_i, divider))
|
||||
taxlevel_f = taxlevel_i / divider
|
||||
return DemurrageCalculator(taxlevel_f)
|
@ -1,144 +0,0 @@
|
||||
"""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()
|
@ -1,134 +0,0 @@
|
||||
"""Deploy sarafu token
|
||||
|
||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
||||
|
||||
"""
|
||||
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
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
|
||||
|
||||
# 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
|
||||
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', 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('--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
|
||||
|
||||
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)
|
||||
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():
|
||||
signer = rpc.get_signer()
|
||||
signer_address = rpc.get_sender_address()
|
||||
|
||||
gas_oracle = rpc.get_gas_oracle()
|
||||
nonce_oracle = rpc.get_nonce_oracle()
|
||||
|
||||
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
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,
|
||||
settings,
|
||||
redistribute=config.true('_MULTI'),
|
||||
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
|
||||
)
|
||||
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']
|
||||
|
||||
print(address)
|
||||
else:
|
||||
print(tx_hash_hex)
|
||||
|
||||
else:
|
||||
print(o)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,2 +0,0 @@
|
||||
from .sim import DemurrageTokenSimulation
|
||||
from .error import TxLimitException
|
@ -1,2 +0,0 @@
|
||||
class TxLimitException(RuntimeError):
|
||||
pass
|
@ -1,302 +0,0 @@
|
||||
# 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 funga.eth.keystore.dict import DictKeystore
|
||||
from funga.eth.signer import 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.redistribute = redistribute
|
||||
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.info('added actor account #{}: {} block {}'.format(i, address, r['block_number']))
|
||||
|
||||
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']
|
||||
logg.info('deployed contract to {} block {}'.format(self.address, r['block_number']))
|
||||
|
||||
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)
|
||||
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.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):
|
||||
o = self.caller_contract.actual_period(self.address, sender_address=self.caller_address)
|
||||
r = self.rpc.do(o)
|
||||
return self.caller_contract.parse_actual_period(r)
|
||||
|
||||
|
||||
def get_demurrage(self):
|
||||
o = self.caller_contract.demurrage_amount(self.address, sender_address=self.caller_address)
|
||||
r = self.rpc.do(o)
|
||||
logg.info('demrrage amount {}'.format(r))
|
||||
return float(self.caller_contract.parse_demurrage_amount(r) / (10 ** 38))
|
||||
|
||||
|
||||
def get_supply(self):
|
||||
o = self.caller_contract.total_supply(self.address, sender_address=self.caller_address)
|
||||
r = self.rpc.do(o)
|
||||
supply = self.caller_contract.parse_total_supply(r)
|
||||
return supply
|
||||
|
||||
|
||||
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(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.info('warping to {}, {} from start {}'.format(target_timestamp, target_timestamp - self.start_timestamp, self.start_timestamp))
|
||||
self.last_timestamp = target_timestamp
|
||||
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
self.last_block = r
|
||||
o = block_by_number(r)
|
||||
r = self.rpc.do(o)
|
||||
cursor_timestamp = r['timestamp'] + 1
|
||||
|
||||
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)
|
||||
|
||||
i = 0
|
||||
while cursor_timestamp < target_timestamp:
|
||||
logg.info('mining block on {}'.format(cursor_timestamp))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[2])
|
||||
self.rpc.do(o)
|
||||
self.eth_helper.time_travel(cursor_timestamp + 60)
|
||||
self.__next_block()
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
if r['status'] == 0:
|
||||
raise RuntimeError('demurrage fast-forward failed on step {} timestamp {} block timestamp {} target {}'.format(i, cursor_timestamp, target_timestamp))
|
||||
cursor_timestamp += 60*60 # 1 hour
|
||||
o = block_by_number(r['block_number'])
|
||||
b = self.rpc.do(o)
|
||||
logg.info('block mined on timestamp {} (delta {}) block number {}'.format(b['timestamp'], b['timestamp'] - self.start_timestamp, b['number']))
|
||||
i += 1
|
||||
|
||||
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[2])
|
||||
self.rpc.do(o)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[3], 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[3])
|
||||
self.rpc.do(o)
|
||||
self.eth_helper.time_travel(target_timestamp + 1)
|
||||
self.__next_block()
|
||||
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
o = block_by_number(self.last_block)
|
||||
r = self.rpc.do(o)
|
||||
self.last_block = r['number']
|
||||
block_base = self.last_block
|
||||
logg.info('block before demurrage execution {} {}'.format(r['timestamp'], r['number']))
|
||||
|
||||
if self.redistribute:
|
||||
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)
|
@ -1,551 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.tx import (
|
||||
TxFactory,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.hash import keccak256_string_to_hex
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
abi_decode_single,
|
||||
)
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.jsonrpc import JSONRPCRequest
|
||||
from eth_erc20 import ERC20
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token.data import data_dir
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DemurrageTokenSettings:
|
||||
|
||||
def __init__(self):
|
||||
self.name = None
|
||||
self.symbol = None
|
||||
self.decimals = None
|
||||
self.demurrage_level = None
|
||||
self.period_minutes = None
|
||||
self.sink_address = None
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return 'name {} demurrage level {} period minutes {} sink address {}'.format(
|
||||
self.name,
|
||||
self.demurrage_level,
|
||||
self.period_minutes,
|
||||
self.sink_address,
|
||||
)
|
||||
|
||||
|
||||
class DemurrageToken(ERC20):
|
||||
|
||||
__abi = {}
|
||||
__bytecode = {}
|
||||
valid_modes = [
|
||||
'MultiNocap',
|
||||
'SingleNocap',
|
||||
'MultiCap',
|
||||
'SingleCap',
|
||||
]
|
||||
|
||||
def constructor(self, sender_address, settings, redistribute=True, cap=0, tx_format=TxFormat.JSONRPC):
|
||||
if int(cap) < 0:
|
||||
raise ValueError('cap must be 0 or positive integer')
|
||||
code = DemurrageToken.bytecode(multi=redistribute, cap=cap>0)
|
||||
enc = ABIContractEncoder()
|
||||
enc.string(settings.name)
|
||||
enc.string(settings.symbol)
|
||||
enc.uint256(settings.decimals)
|
||||
enc.uint256(settings.demurrage_level)
|
||||
enc.uint256(settings.period_minutes)
|
||||
enc.address(settings.sink_address)
|
||||
if cap > 0:
|
||||
enc.uint256(cap)
|
||||
code += enc.get()
|
||||
tx = self.template(sender_address, None, use_nonce=True)
|
||||
tx = self.set_code(tx, code)
|
||||
return self.finalize(tx, tx_format)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def gas(code=None):
|
||||
return 4000000
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __to_contract_name(multi, cap):
|
||||
name = 'DemurrageToken'
|
||||
if multi:
|
||||
name += 'Multi'
|
||||
else:
|
||||
name += 'Single'
|
||||
if cap:
|
||||
name += 'Cap'
|
||||
else:
|
||||
name += 'Nocap'
|
||||
logg.debug('bytecode name {}'.format(name))
|
||||
return name
|
||||
|
||||
|
||||
@staticmethod
|
||||
def abi(multi=True, cap=False):
|
||||
name = DemurrageToken.__to_contract_name(multi, cap)
|
||||
if DemurrageToken.__abi.get(name) == None:
|
||||
f = open(os.path.join(data_dir, name + '.json'), 'r')
|
||||
DemurrageToken.__abi[name] = json.load(f)
|
||||
f.close()
|
||||
return DemurrageToken.__abi[name]
|
||||
|
||||
|
||||
@staticmethod
|
||||
def bytecode(multi=True, cap=False):
|
||||
name = DemurrageToken.__to_contract_name(multi, cap)
|
||||
if DemurrageToken.__bytecode.get(name) == None:
|
||||
f = open(os.path.join(data_dir, name + '.bin'), 'r')
|
||||
DemurrageToken.__bytecode[name] = f.read()
|
||||
f.close()
|
||||
return DemurrageToken.__bytecode[name]
|
||||
|
||||
|
||||
def add_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('addMinter')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
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 remove_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('removeMinter')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
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 mint_to(self, contract_address, sender_address, address, value, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('mintTo')
|
||||
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 to_base_amount(self, contract_address, value, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toBaseAmount')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(value)
|
||||
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 remainder(self, contract_address, parts, whole, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('remainder')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(parts)
|
||||
enc.uint256(whole)
|
||||
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 redistributions(self, contract_address, idx, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('redistributions')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(idx)
|
||||
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 account_period(self, contract_address, address, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('accountPeriod')
|
||||
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')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
|
||||
def to_redistribution(self, contract_address, participants, demurrage_modifier_ppm, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toRedistribution')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(participants)
|
||||
enc.uint256(demurrage_modifier_ppm)
|
||||
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 to_redistribution_period(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('toRedistributionPeriod')
|
||||
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')
|
||||
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_supply(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('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')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
|
||||
def to_redistribution_demurrage_modifier(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('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')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
|
||||
def base_balance_of(self, contract_address, address, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.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')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
|
||||
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):
|
||||
return self.transact_noarg('changePeriod', contract_address, sender_address)
|
||||
|
||||
|
||||
def apply_redistribution_on_account(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('applyRedistributionOnAccount')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
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 tax_level(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('taxLevel', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def resolution_factor(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('resolutionFactor', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def actual_period(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('actualPeriod', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def period_start(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('periodStart', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def period_duration(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('periodDuration', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def demurrage_amount(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('demurrageAmount', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def demurrage_timestamp(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('demurrageTimestamp', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def supply_cap(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
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 decay_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('decayBy')
|
||||
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 get_distribution(self, contract_address, supply, demurrage_amount, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('getDistribution')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(supply)
|
||||
enc.uint256(demurrage_amount)
|
||||
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 get_distribution_from_redistribution(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('getDistributionFromRedistribution')
|
||||
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
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_actual_period(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_period_start(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_period_duration(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_demurrage_amount(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_remainder(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_to_base_amount(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_redistributions(self, v):
|
||||
return abi_decode_single(ABIContractType.BYTES32, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_account_period(self, v):
|
||||
return abi_decode_single(ABIContractType.ADDRESS, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_to_redistribution_period(self, v):
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def parse_grow_by(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_decay_by(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_get_distribution(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_tax_level(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_resolution_factor(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
@ -1 +0,0 @@
|
||||
from .base import *
|
@ -1,259 +0,0 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import os
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.unittest.ethtester import EthTesterCase
|
||||
from chainlib.eth.tx import (
|
||||
receipt,
|
||||
)
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
)
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import (
|
||||
DemurrageTokenSettings,
|
||||
DemurrageToken,
|
||||
)
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
#BLOCKTIME = 5 # seconds
|
||||
TAX_LEVEL = int(10000 * 2) # 2%
|
||||
# calc "1-(0.98)^(1/518400)" <- 518400 = 30 days of blocks
|
||||
# 0.00000003897127107225
|
||||
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
|
||||
PERIOD = 10
|
||||
|
||||
|
||||
class TestTokenDeploy:
|
||||
|
||||
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.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)
|
||||
|
||||
o = block_by_number(self.start_block, include_tx=False)
|
||||
r = rpc.do(o)
|
||||
|
||||
try:
|
||||
self.start_time = int(r['timestamp'], 16)
|
||||
except TypeError:
|
||||
self.start_time = int(r['timestamp'])
|
||||
|
||||
self.default_supply = supply
|
||||
self.default_supply_cap = int(self.default_supply * 10)
|
||||
|
||||
|
||||
def deploy(self, rpc, deployer_address, interface, mode, supply_cap=10**12):
|
||||
tx_hash = None
|
||||
o = None
|
||||
logg.debug('mode {} {}'.format(mode, self.settings))
|
||||
self.mode = mode
|
||||
if mode == 'MultiNocap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=True, cap=0)
|
||||
elif mode == 'SingleNocap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=0)
|
||||
elif mode == 'MultiCap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=True, cap=supply_cap)
|
||||
elif mode == 'SingleCap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=supply_cap)
|
||||
else:
|
||||
raise ValueError('Invalid mode "{}", valid are {}'.format(mode, DemurrageToken.valid_modes))
|
||||
|
||||
r = rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
self.start_block = r['block_number']
|
||||
self.address = r['contract_address']
|
||||
|
||||
o = block_by_number(r['block_number'])
|
||||
r = rpc.do(o)
|
||||
self.start_time = r['timestamp']
|
||||
|
||||
return self.address
|
||||
|
||||
|
||||
class TestDemurrage(EthTesterCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrage, self).setUp()
|
||||
# token_deploy = TestTokenDeploy()
|
||||
# self.settings = token_deploy.settings
|
||||
# self.sink_address = token_deploy.sink_address
|
||||
# self.start_block = token_deploy.start_block
|
||||
# self.start_time = token_deploy.start_time
|
||||
# self.default_supply = self.default_supply
|
||||
# self.default_supply_cap = self.default_supply_cap
|
||||
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
|
||||
self.address = None
|
||||
self.start_time = None
|
||||
|
||||
|
||||
def deploy(self, interface, mode):
|
||||
self.address = self.deployer.deploy(self.rpc, self.accounts[0], interface, mode, supply_cap=self.default_supply_cap)
|
||||
self.start_block = self.deployer.start_block
|
||||
self.start_time = self.deployer.start_time
|
||||
self.tax_level = self.deployer.tax_level
|
||||
self.period_seconds = self.deployer.period_seconds
|
||||
self.sink_address = self.deployer.sink_address
|
||||
|
||||
logg.debug('contract address {} start block {} start time {}'.format(self.address, self.start_block, self.start_time))
|
||||
|
||||
|
||||
def assert_within_lower(self, v, target, tolerance_ppm):
|
||||
lower_target = target - (target * (tolerance_ppm / 1000000))
|
||||
self.assertGreaterEqual(v, lower_target)
|
||||
self.assertLessEqual(v, target)
|
||||
logg.debug('asserted within lower {} <= {} <= {}'.format(lower_target, v, target))
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestDemurrageDefault(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrageDefault, 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.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
|
||||
class TestDemurrageSingle(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrageSingle, 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')
|
||||
single_valid_modes = [
|
||||
'SingleNocap',
|
||||
'SingleCap',
|
||||
]
|
||||
if self.mode != None:
|
||||
if self.mode not in single_valid_modes:
|
||||
raise ValueError('Invalid mode "{}" for "single" contract tests, valid are {}'.format(self.mode, single_valid_modes))
|
||||
else:
|
||||
self.mode = 'SingleNocap'
|
||||
logg.debug('executing test setup demurragesingle mode {}'.format(self.mode))
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
|
||||
class TestDemurrageCap(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrageCap, 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')
|
||||
cap_valid_modes = [
|
||||
'MultiCap',
|
||||
'SingleCap',
|
||||
]
|
||||
if self.mode != None:
|
||||
if self.mode not in cap_valid_modes:
|
||||
raise ValueError('Invalid mode "{}" for "cap" contract tests, valid are {}'.format(self.mode, cap_valid_modes))
|
||||
else:
|
||||
self.mode = 'MultiCap'
|
||||
logg.debug('executing test setup demurragecap mode {}'.format(self.mode))
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
|
||||
|
||||
class TestDemurrageUnit(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
self.period = 1
|
||||
self.period_seconds = self.period * 60
|
||||
self.tax_level = TAX_LEVEL
|
||||
|
||||
super(TestDemurrageUnit, self).setUp()
|
||||
|
||||
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 * (10 ** 32)
|
||||
self.settings.period_minutes = int(self.period_seconds/60)
|
||||
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')
|
||||
unit_valid_modes = [
|
||||
'SingleNocap',
|
||||
'SingleCap',
|
||||
]
|
||||
if self.mode != None:
|
||||
if self.mode not in unit_valid_modes:
|
||||
raise ValueError('Invalid mode "{}" for "unit" contract tests, valid are {}'.format(self.mode, unit_valid_modes))
|
||||
else:
|
||||
self.mode = 'SingleNocap'
|
||||
logg.debug('executing test setup unit mode {}'.format(self.mode))
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
@ -1,76 +0,0 @@
|
||||
# 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 = (10 ** 6) * (10 ** 12)
|
||||
|
||||
# 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)))
|
||||
print('sink balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(sim.sink_address), sim.balance(sim.sink_address, 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))
|
@ -1,80 +0,0 @@
|
||||
# standard imports
|
||||
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()
|
||||
|
||||
decay_per_minute = 0.00000050105908373373 # 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**38))
|
||||
#settings.period_minutes = 1 # 1 week in minutes
|
||||
settings.period_minutes = 60*24*7
|
||||
chain = 'evm:foochain:42'
|
||||
cap = (10 ** 6) * (10 ** 12)
|
||||
#cap = 0
|
||||
|
||||
# instantiate simulation
|
||||
sim = DemurrageTokenSimulation(chain, settings, redistribute=False, 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)))
|
||||
print('sink balance: demurraged {:>9d} base {:>9d}'.format(sim.balance(sim.sink_address), sim.balance(sim.sink_address, base=True)))
|
||||
|
||||
# get times
|
||||
minutes = sim.get_minutes()
|
||||
timestamp = sim.get_now()
|
||||
start = sim.get_start()
|
||||
period = sim.get_period()
|
||||
print('start {} now {} period {} minutes passed {}'.format(start, timestamp, period, minutes))
|
||||
|
||||
|
||||
contract_demurrage = 1 - sim.get_demurrage() # demurrage in percent (float)
|
||||
frontend_demurrage = 1.0 - ((1 - decay_per_minute) ** minutes) # 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 {}
|
||||
print("""alice contract balance {}
|
||||
frontend demurrage {:.38f}
|
||||
contract demurrage {:.38f}
|
||||
demurrage delta {:.38f}""".format(
|
||||
alice_checksum,
|
||||
sim.balance(alice),
|
||||
frontend_demurrage,
|
||||
contract_demurrage,
|
||||
demurrage_delta),
|
||||
)
|
||||
|
||||
balance_sum = sim.balance(alice) + sim.balance(bob) + sim.balance(carol) + sim.balance(sim.sink_address)
|
||||
supply = sim.get_supply()
|
||||
print('sum of contract demurraged balances {}'.format(balance_sum))
|
||||
print('total token supply {}'.format(supply))
|
@ -1,3 +1,3 @@
|
||||
chainlib-eth>=0.1.0,<0.2.0
|
||||
eth-erc20~=0.3.0
|
||||
funga-eth~=0.6.0
|
||||
chainlib~=0.0.3a1
|
||||
eth-erc20~=0.0.9a1
|
||||
crypto-dev-signer~=0.4.14b3
|
||||
|
@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
export PYTHONPATH=.
|
||||
|
||||
#modes=(MultiNocap MultiCap SingleCap SingleNocap)
|
||||
modes=(SingleCap 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
|
||||
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
|
||||
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)
|
||||
for m in ${modes[@]}; do
|
||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_cap.py
|
||||
done
|
||||
|
||||
#modes=(MultiCap MultiNocap)
|
||||
#for m in ${modes[@]}; do
|
||||
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_remainder.py
|
||||
# ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_redistribution.py
|
||||
#done
|
||||
|
||||
set +e
|
||||
set +x
|
1
python/sarafu_token/__init__.py
Normal file
1
python/sarafu_token/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .token import RedistributedDemurrageToken
|
1
python/sarafu_token/data/RedistributedDemurrageToken.bin
Normal file
1
python/sarafu_token/data/RedistributedDemurrageToken.bin
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
135
python/sarafu_token/runnable/deploy.py
Normal file
135
python/sarafu_token/runnable/deploy.py
Normal file
@ -0,0 +1,135 @@
|
||||
"""Deploy sarafu token
|
||||
|
||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
||||
|
||||
"""
|
||||
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
# third-party imports
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
from crypto_dev_signer.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
|
||||
|
||||
# local imports
|
||||
from sarafu_token import RedistributedDemurrageToken
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
data_dir = os.path.join(script_dir, '..', 'data')
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
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')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='evm:ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('-vv', action='store_true', help='Be more verbose')
|
||||
argparser.add_argument('-d', action='store_true', help='Dump RPC calls to terminal and do not send')
|
||||
argparser.add_argument('--name', type=str, help='Token name')
|
||||
argparser.add_argument('--decimals', default=6, type=int, help='Token decimals')
|
||||
argparser.add_argument('--gas-price', type=int, dest='gas_price', help='Override gas price')
|
||||
argparser.add_argument('--nonce', type=int, help='Override transaction nonce')
|
||||
argparser.add_argument('--sink-address', 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('--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')
|
||||
args = argparser.parse_args()
|
||||
|
||||
if args.vv:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
elif args.v:
|
||||
logg.setLevel(logging.INFO)
|
||||
|
||||
block_last = args.w
|
||||
block_all = args.ww
|
||||
|
||||
passphrase_env = 'ETH_PASSPHRASE'
|
||||
if args.env_prefix != None:
|
||||
passphrase_env = args.env_prefix + '_' + passphrase_env
|
||||
passphrase = os.environ.get(passphrase_env)
|
||||
if passphrase == None:
|
||||
logg.warning('no passphrase given')
|
||||
passphrase=''
|
||||
|
||||
signer_address = None
|
||||
keystore = DictKeystore()
|
||||
if args.y != None:
|
||||
logg.debug('loading keystore file {}'.format(args.y))
|
||||
signer_address = keystore.import_keystore_file(args.y, password=passphrase)
|
||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||
signer = EIP155Signer(keystore)
|
||||
|
||||
chain_spec = ChainSpec.from_chain_str(args.i)
|
||||
|
||||
rpc = EthHTTPConnection(args.p)
|
||||
nonce_oracle = None
|
||||
if args.nonce != None:
|
||||
nonce_oracle = OverrideNonceOracle(signer_address, args.nonce)
|
||||
else:
|
||||
nonce_oracle = RPCNonceOracle(signer_address, rpc)
|
||||
|
||||
gas_oracle = None
|
||||
if args.gas_price !=None:
|
||||
gas_oracle = OverrideGasOracle(price=args.gas_price, conn=rpc, code_callback=RedistributedDemurrageToken.gas)
|
||||
else:
|
||||
gas_oracle = RPCGasOracle(rpc, code_callback=RedistributedDemurrageToken.gas)
|
||||
|
||||
dummy = args.d
|
||||
|
||||
token_name = args.name
|
||||
if token_name == None:
|
||||
token_name = args.symbol
|
||||
|
||||
def main():
|
||||
c = RedistributedDemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
(tx_hash_hex, o) = c.constructor(
|
||||
signer_address,
|
||||
token_name,
|
||||
args.symbol,
|
||||
args.decimals,
|
||||
args.demurrage_level,
|
||||
args.redistribution_period,
|
||||
args.sink_address,
|
||||
)
|
||||
if dummy:
|
||||
print(tx_hash_hex)
|
||||
print(o)
|
||||
else:
|
||||
rpc.do(o)
|
||||
if block_last:
|
||||
r = rpc.wait(tx_hash_hex)
|
||||
if r['status'] == 0:
|
||||
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')
|
||||
sys.exit(1)
|
||||
# TODO: pass through translator for keys (evm tester uses underscore instead of camelcase)
|
||||
address = r['contractAddress']
|
||||
|
||||
print(address)
|
||||
else:
|
||||
print(tx_hash_hex)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
133
python/sarafu_token/runnable/legacy/deploy.py
Normal file
133
python/sarafu_token/runnable/legacy/deploy.py
Normal file
@ -0,0 +1,133 @@
|
||||
"""Deploys Sarafu token
|
||||
|
||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
||||
|
||||
"""
|
||||
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
import time
|
||||
from enum import Enum
|
||||
|
||||
# third-party imports
|
||||
import web3
|
||||
from crypto_dev_signer.eth.signer import ReferenceSigner as EIP155Signer
|
||||
from crypto_dev_signer.keystore import DictKeystore
|
||||
from crypto_dev_signer.eth.helper import EthTxExecutor
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logg = logging.getLogger()
|
||||
|
||||
logging.getLogger('web3').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
data_dir = os.path.join(script_dir, '..', '..', 'data')
|
||||
|
||||
|
||||
argparser = argparse.ArgumentParser()
|
||||
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')
|
||||
argparser.add_argument('-e', action='store_true', help='Treat all transactions as essential')
|
||||
argparser.add_argument('-i', '--chain-spec', dest='i', type=str, default='Ethereum:1', help='Chain specification string')
|
||||
argparser.add_argument('-y', '--key-file', dest='y', type=str, help='Ethereum keystore file to use for signing')
|
||||
argparser.add_argument('--name', dest='n', default='Giftable Token', type=str, help='Token name')
|
||||
argparser.add_argument('--symbol', dest='s', default='GFT', type=str, help='Token symbol')
|
||||
argparser.add_argument('--decimals', dest='d', default=18, type=int, help='Token decimals')
|
||||
argparser.add_argument('--minter', action='append', type=str, help='Minter to add')
|
||||
argparser.add_argument('--sink-address', type=str, help='Sink address (if not set, signer address is used)')
|
||||
argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=data_dir, help='Directory containing bytecode and abi (default: {})'.format(data_dir))
|
||||
|
||||
argparser.add_argument('-v', action='store_true', help='Be verbose')
|
||||
argparser.add_argument('taxlevel_minute', type=int, help='Tax level per minute in ppm')
|
||||
argparser.add_argument('period_minutes', type=int, help='Redistribution period, in minutes')
|
||||
args = argparser.parse_args()
|
||||
|
||||
if args.v:
|
||||
logg.setLevel(logging.DEBUG)
|
||||
|
||||
block_last = args.w
|
||||
block_all = args.ww
|
||||
|
||||
w3 = web3.Web3(web3.Web3.HTTPProvider(args.p))
|
||||
|
||||
signer_address = None
|
||||
keystore = DictKeystore()
|
||||
if args.y != None:
|
||||
logg.debug('loading keystore file {}'.format(args.y))
|
||||
signer_address = keystore.import_keystore_file(args.y)
|
||||
logg.debug('now have key for signer address {}'.format(signer_address))
|
||||
signer = EIP155Signer(keystore)
|
||||
|
||||
chain_pair = args.i.split(':')
|
||||
chain_id = int(chain_pair[1])
|
||||
|
||||
helper = EthTxExecutor(
|
||||
w3,
|
||||
signer_address,
|
||||
signer,
|
||||
chain_id,
|
||||
block=args.ww,
|
||||
)
|
||||
#g = ERC20TxFactory(signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle, chain_id=chain_id)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
f = open(os.path.join(args.abi_dir, 'RedistributedDemurrageToken.json'), 'r')
|
||||
abi = json.load(f)
|
||||
f.close()
|
||||
|
||||
f = open(os.path.join(args.abi_dir, 'RedistributedDemurrageToken.bin'), 'r')
|
||||
bytecode = f.read()
|
||||
f.close()
|
||||
|
||||
sink_address = args.sink_address
|
||||
if sink_address == None:
|
||||
sink_address = signer_address
|
||||
|
||||
c = w3.eth.contract(abi=abi, bytecode=bytecode)
|
||||
(tx_hash, rcpt) = helper.sign_and_send(
|
||||
[
|
||||
c.constructor(args.n, args.s, args.d, args.taxlevel_minute, args.period_minutes, sink_address).buildTransaction
|
||||
],
|
||||
force_wait=True,
|
||||
)
|
||||
logg.debug('tx hash {} rcpt {}'.format(tx_hash, rcpt))
|
||||
|
||||
address = rcpt.contractAddress
|
||||
logg.debug('token contract mined {} {} {} {}'.format(address, args.n, args.s, args.d, args.taxlevel_minute, args.period_minutes, sink_address))
|
||||
c = w3.eth.contract(abi=abi, address=address)
|
||||
|
||||
balance = c.functions.balanceOf(signer_address).call()
|
||||
logg.info('balance {}: {} {}'.format(signer_address, balance, tx_hash))
|
||||
|
||||
if args.minter != None:
|
||||
for a in args.minter:
|
||||
if a == signer_address:
|
||||
continue
|
||||
(tx_hash, rcpt) = helper.sign_and_send(
|
||||
[
|
||||
c.functions.addMinter(a).buildTransaction,
|
||||
],
|
||||
)
|
||||
logg.debug('minter add {} {}'.format(a, tx_hash))
|
||||
|
||||
if block_last:
|
||||
helper.wait_for()
|
||||
|
||||
print(address)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
87
python/sarafu_token/token.py
Normal file
87
python/sarafu_token/token.py
Normal file
@ -0,0 +1,87 @@
|
||||
# standard imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.tx import (
|
||||
TxFactory,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.hash import keccak256_string_to_hex
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from sarafu_token.data import data_dir
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RedistributedDemurrageToken(TxFactory):
|
||||
|
||||
__abi = None
|
||||
__bytecode = None
|
||||
|
||||
def constructor(self, sender_address, name, symbol, decimals, demurrage_level, period_minutes, sink_address, tx_format=TxFormat.JSONRPC):
|
||||
code = RedistributedDemurrageToken.bytecode()
|
||||
enc = ABIContractEncoder()
|
||||
enc.string(name)
|
||||
enc.string(symbol)
|
||||
enc.uint256(decimals)
|
||||
enc.uint256(demurrage_level)
|
||||
enc.uint256(period_minutes)
|
||||
enc.address(sink_address)
|
||||
code += enc.get()
|
||||
tx = self.template(sender_address, None, use_nonce=True)
|
||||
tx = self.set_code(tx, code)
|
||||
return self.finalize(tx, tx_format)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def gas(code=None):
|
||||
return 3500000
|
||||
|
||||
@staticmethod
|
||||
def abi():
|
||||
if RedistributedDemurrageToken.__abi == None:
|
||||
f = open(os.path.join(data_dir, 'RedistributedDemurrageToken.json'), 'r')
|
||||
RedistributedDemurrageToken.__abi = json.load(f)
|
||||
f.close()
|
||||
return RedistributedDemurrageToken.__abi
|
||||
|
||||
|
||||
@staticmethod
|
||||
def bytecode():
|
||||
if RedistributedDemurrageToken.__bytecode == None:
|
||||
f = open(os.path.join(data_dir, 'RedistributedDemurrageToken.bin'), 'r')
|
||||
RedistributedDemurrageToken.__bytecode = f.read()
|
||||
f.close()
|
||||
return RedistributedDemurrageToken.__bytecode
|
||||
|
||||
|
||||
def add_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('addMinter')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
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 mint_to(self, contract_address, sender_address, address, value, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('mintTo')
|
||||
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
|
@ -1,15 +1,12 @@
|
||||
[metadata]
|
||||
name = erc20-demurrage-token
|
||||
version = 0.1.1
|
||||
name = sarafu-token
|
||||
version = 0.0.1a8
|
||||
description = ERC20 token with redistributed continual demurrage
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
url = https://gitlab.com/ccicnet/erc20-demurrage-token
|
||||
url = https://gitlab.com/grassrootseconomics/sarafu-token
|
||||
keywords =
|
||||
ethereum
|
||||
blockchain
|
||||
cryptocurrency
|
||||
erc20
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
Operating System :: OS Independent
|
||||
@ -25,20 +22,20 @@ licence_files =
|
||||
|
||||
[options]
|
||||
include_package_data = True
|
||||
python_requires = >= 3.7
|
||||
python_requires = >= 3.6
|
||||
packages =
|
||||
erc20_demurrage_token
|
||||
erc20_demurrage_token.runnable
|
||||
erc20_demurrage_token.data
|
||||
erc20_demurrage_token.sim
|
||||
erc20_demurrage_token.unittest
|
||||
sarafu_token
|
||||
sarafu_token.runnable
|
||||
install_requires =
|
||||
chainlib~=0.0.3a1
|
||||
crypto-dev-signer~=0.4.14b3
|
||||
|
||||
|
||||
[options.package_data]
|
||||
* =
|
||||
data/DemurrageToken*.bin
|
||||
data/DemurrageToken*.json
|
||||
data/RedistributedDemurrageToken.bin
|
||||
data/RedistributedDemurrageToken.json
|
||||
|
||||
[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
|
||||
sarafu-token-deploy = sarafu_token.runnable.deploy:main
|
||||
|
@ -1,2 +1,3 @@
|
||||
web3==5.12.2
|
||||
eth_tester==0.5.0b3
|
||||
py-evm==0.3.0a20
|
||||
|
@ -1,73 +0,0 @@
|
||||
# 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()
|
@ -1,35 +0,0 @@
|
||||
# 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()
|
@ -1,123 +0,0 @@
|
||||
# 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
|
||||
|
||||
# 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 TestAmounts(TestDemurrageDefault):
|
||||
|
||||
def test_mints(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], 1000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, 817)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assert_within_lower(balance, 1817, 750)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds * 2)
|
||||
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
|
||||
expected_balance = ((1 - self.tax_level / 1000000) ** 10) * 1000
|
||||
expected_balance += ((1 - self.tax_level / 1000000) ** 20) * 1000
|
||||
self.assert_within_lower(balance, expected_balance, 500)
|
||||
|
||||
|
||||
def test_transfers(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], 2000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, 1634)
|
||||
|
||||
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[2], 500)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, 1134)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assert_within_lower(balance, 500, 2000)
|
||||
|
||||
|
||||
def test_dynamic_amount(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], 2000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
cases = [
|
||||
(61, 1960),
|
||||
(121, 1920),
|
||||
(181, 1882),
|
||||
(241, 1844),
|
||||
(301, 1807),
|
||||
(361, 1771),
|
||||
(421, 1736),
|
||||
(481, 1701),
|
||||
(541, 1667),
|
||||
(601, 1634),
|
||||
]
|
||||
for case in cases:
|
||||
self.backend.time_travel(self.start_time + case[0])
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, case[1])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -5,315 +5,206 @@ 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 TestDemurrageDefault
|
||||
# third-party imports
|
||||
import web3
|
||||
import eth_tester
|
||||
import eth_abi
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
logging.getLogger('web3').setLevel(logging.WARNING)
|
||||
logging.getLogger('eth.vm').setLevel(logging.WARNING)
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
#BLOCKTIME = 5 # seconds
|
||||
TAX_LEVEL = int(10000 * 2) # 2%
|
||||
# calc "1-(0.98)^(1/518400)" <- 518400 = 30 days of blocks
|
||||
# 0.00000003897127107225
|
||||
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
|
||||
PERIOD = 1
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
contract = None
|
||||
|
||||
def setUp(self):
|
||||
eth_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
|
||||
'gas_limit': 9000000,
|
||||
})
|
||||
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.bin'), 'r')
|
||||
self.bytecode = f.read()
|
||||
f.close()
|
||||
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.json'), 'r')
|
||||
self.abi = json.load(f)
|
||||
f.close()
|
||||
|
||||
|
||||
backend = eth_tester.PyEVMBackend(eth_params)
|
||||
self.eth_tester = eth_tester.EthereumTester(backend)
|
||||
provider = web3.Web3.EthereumTesterProvider(self.eth_tester)
|
||||
self.w3 = web3.Web3(provider)
|
||||
self.sink_address = self.w3.eth.accounts[9]
|
||||
|
||||
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
|
||||
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
|
||||
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
|
||||
|
||||
self.start_block = self.w3.eth.blockNumber
|
||||
b = self.w3.eth.getBlock(self.start_block)
|
||||
self.start_time = b['timestamp']
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
class TestBasic(TestDemurrageDefault):
|
||||
|
||||
def test_hello(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds + 1)
|
||||
o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
|
||||
|
||||
def test_balance(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], 1024)
|
||||
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, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
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)
|
||||
self.assertEqual(self.contract.functions.actualPeriod().call(), 1)
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
self.assertEqual(self.contract.functions.actualPeriod().call(), 2)
|
||||
|
||||
|
||||
def test_apply_demurrage(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)
|
||||
modifier = 10 * (10 ** 37)
|
||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
||||
#demurrage_modifier &= (1 << 128) - 1
|
||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
||||
#self.assertEqual(modifier, demurrage_modifier)
|
||||
self.assertEqual(modifier, demurrage_amount)
|
||||
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
o = block_by_number(r)
|
||||
b = self.rpc.do(o)
|
||||
logg.debug('block {} start {}'.format(b['timestamp'], self.start_time))
|
||||
|
||||
self.backend.time_travel(self.start_time + 2)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_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.demurrage_amount(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage_amount = c.parse_demurrage_amount(r)
|
||||
self.eth_tester.time_travel(self.start_time + 59)
|
||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
||||
#demurrage_modifier &= (1 << 128) - 1
|
||||
#self.assertEqual(modifier, demurrage_modifier)
|
||||
self.assertEqual(modifier, demurrage_amount)
|
||||
|
||||
self.backend.time_travel(self.start_time + 61)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_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.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)
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
tx_hash = self.contract.functions.applyDemurrage().transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
|
||||
self.backend.time_travel(self.start_time + 601)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_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.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) / 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 ** 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)
|
||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
||||
#demurrage_modifier &= (1 << 128) - 1
|
||||
#self.assertEqual(int(98 * (10 ** 36)), demurrage_modifier)
|
||||
self.assertEqual(int(98 * (10 ** 36)), demurrage_amount)
|
||||
|
||||
|
||||
def test_mint(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], 1024)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance, 1024)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 976)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 976).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance, 2000)
|
||||
|
||||
|
||||
self.backend.time_travel(self.start_time + 61)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance, int(2000 * 0.98))
|
||||
|
||||
|
||||
def test_minter_control(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[2], 1024).transact({'from': self.w3.eth.accounts[1]})
|
||||
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
tx_hash = self.contract.functions.addMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[1]})
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[1], self.accounts[2], 1024)
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
(tx_hash, o) = c.add_minter(self.address, self.accounts[1], self.accounts[1])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
tx_hash = self.contract.functions.addMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[0]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
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)
|
||||
(tx_hash, o) = c.add_minter(self.address, self.accounts[0], self.accounts[1])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
tx_hash = self.contract.functions.addMinter(self.w3.eth.accounts[2]).transact({'from': self.w3.eth.accounts[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.mint_to(self.address, self.accounts[1], self.accounts[2], 1024)
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[2], 1024).transact({'from': self.w3.eth.accounts[1]})
|
||||
|
||||
(tx_hash, o) = c.add_minter(self.address, self.accounts[1], self.accounts[2])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
tx_hash = self.contract.functions.addMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[2]})
|
||||
|
||||
(tx_hash, o) = c.remove_minter(self.address, self.accounts[1], self.accounts[1])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
tx_hash = self.contract.functions.removeMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[1]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[2], 1024).transact({'from': self.w3.eth.accounts[1]})
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[1], self.accounts[2], 1024)
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
def test_base_amount(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
self.rpc.do(o)
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
|
||||
self.backend.time_travel(self.start_time + 61)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = c.to_base_amount(self.address, 1000, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
amount = c.parse_to_base_amount(r)
|
||||
self.assertEqual(amount, 1020)
|
||||
self.contract.functions.applyDemurrage().transact()
|
||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
||||
#demurrage_amount = self.contract.functions.toDemurrageAmount(demurrage_modifier).call()
|
||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
||||
|
||||
a = self.contract.functions.toBaseAmount(1000).call();
|
||||
self.assertEqual(a, 1020)
|
||||
|
||||
|
||||
def test_transfer(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
self.rpc.do(o)
|
||||
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
logg.debug('tx {}'.format(r))
|
||||
|
||||
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[2], 500)
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance_alice, 524)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.assertEqual(balance, 524)
|
||||
balance_bob = self.contract.functions.balanceOf(self.w3.eth.accounts[2]).call()
|
||||
self.assertEqual(balance_bob, 500)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.assertEqual(balance, 500)
|
||||
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
logg.debug('tx {}'.format(r))
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[2], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer(self.address, self.accounts[2], self.accounts[1], 500)
|
||||
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)
|
||||
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], 1024)
|
||||
self.rpc.do(o)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
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.approve(self.address, self.accounts[1], self.accounts[2], 500)
|
||||
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.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.assertEqual(balance, 1024)
|
||||
tx_hash = self.contract.functions.approve(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
logg.debug('tx {}'.format(r))
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[2], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer_from(self.address, self.accounts[2], self.accounts[1], self.accounts[3], 500)
|
||||
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.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.assertEqual(balance, 524)
|
||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance_alice, 1024)
|
||||
|
||||
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, 500)
|
||||
tx_hash = self.contract.functions.transferFrom(self.w3.eth.accounts[1], self.w3.eth.accounts[3], 500).transact({'from': self.w3.eth.accounts[2]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
logg.debug('tx {}'.format(r))
|
||||
|
||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance_alice, 524)
|
||||
|
||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[3]).call()
|
||||
self.assertEqual(balance_alice, 500)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,67 +0,0 @@
|
||||
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
|
||||
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 TestDemurrageCap
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestCap(TestDemurrageCap):
|
||||
|
||||
def test_cap_set(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
o = c.supply_cap(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
cap = c.parse_supply_cap(r)
|
||||
self.assertEqual(cap, self.default_supply_cap)
|
||||
|
||||
|
||||
def test_cap(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_cap)
|
||||
r = 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], 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
def test_cap_first(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_cap + 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,41 +0,0 @@
|
||||
# standard imports
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
from erc20_demurrage_token.demurrage import DemurrageCalculator
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrage
|
||||
|
||||
|
||||
class TestEmulate(TestDemurrage):
|
||||
|
||||
def test_amount_since(self):
|
||||
d = datetime.datetime.utcnow() - datetime.timedelta(seconds=29, hours=5, minutes=3, days=4)
|
||||
c = DemurrageCalculator(0.00000050105908373373)
|
||||
a = c.amount_since(100, d.timestamp())
|
||||
self.assert_within_lower(a, 99.69667, 0.1)
|
||||
|
||||
|
||||
def test_amount_since_slow(self):
|
||||
d = datetime.datetime.utcnow() - datetime.timedelta(seconds=29, hours=5, minutes=3, days=4)
|
||||
c = DemurrageCalculator(0.00000050105908373373)
|
||||
a = c.amount_since_slow(100, d.timestamp())
|
||||
self.assert_within_lower(a, 99.69667, 0.1)
|
||||
|
||||
|
||||
def test_from_contract(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
self.deploy(c, 'SingleNocap')
|
||||
dc = DemurrageCalculator.from_contract(self.rpc, self.chain_spec, self.address, sender_address=self.accounts[0])
|
||||
self.assertEqual(dc.r_min, 0.02)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,70 +0,0 @@
|
||||
# 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 TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
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_decay_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.decay_by(self.address, v, 1, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
g = c.parse_decay_by(r)
|
||||
self.assertEqual(int(v * growth_factor), g)
|
||||
|
||||
period = 10
|
||||
growth_factor = (1 - (self.tax_level) / 1000000) ** period
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -4,196 +4,76 @@ 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,
|
||||
TxFactory,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
# third-party imports
|
||||
import web3
|
||||
import eth_tester
|
||||
import eth_abi
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
logging.getLogger('web3').setLevel(logging.WARNING)
|
||||
logging.getLogger('eth.vm').setLevel(logging.WARNING)
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
class TestPeriod(TestDemurrageDefault):
|
||||
#BLOCKTIME = 5 # seconds
|
||||
TAX_LEVEL = 10000 * 2 # 2%
|
||||
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
|
||||
PERIOD = 1
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
contract = None
|
||||
|
||||
def setUp(self):
|
||||
eth_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
|
||||
'gas_limit': 9000000,
|
||||
})
|
||||
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.bin'), 'r')
|
||||
self.bytecode = f.read()
|
||||
f.close()
|
||||
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.json'), 'r')
|
||||
self.abi = json.load(f)
|
||||
f.close()
|
||||
|
||||
backend = eth_tester.PyEVMBackend(eth_params)
|
||||
self.eth_tester = eth_tester.EthereumTester(backend)
|
||||
provider = web3.Web3.EthereumTesterProvider(self.eth_tester)
|
||||
self.w3 = web3.Web3(provider)
|
||||
self.sink_address = self.w3.eth.accounts[9]
|
||||
|
||||
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
|
||||
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
|
||||
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
|
||||
|
||||
self.start_block = self.w3.eth.blockNumber
|
||||
b = self.w3.eth.getBlock(self.start_block)
|
||||
self.start_time = b['timestamp']
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_period(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], 1024)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
tx_hash = self.contract.functions.changePeriod().transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 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.redistributions(self.address, 1, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
redistribution = c.parse_redistributions(r)
|
||||
|
||||
o = c.to_redistribution_period(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_period(r)
|
||||
self.assertEqual(2, period)
|
||||
|
||||
o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
redistribution = c.parse_redistributions(r)
|
||||
|
||||
o = c.to_redistribution_period(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_period(r)
|
||||
self.assertEqual(2, period)
|
||||
|
||||
o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_actual_period(r)
|
||||
self.assertEqual(2, period)
|
||||
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_item(r)
|
||||
|
||||
# allow test code float rounding error to billionth
|
||||
modifier = (1 - (self.tax_level / 1000000)) ** (self.period_seconds / 60)
|
||||
modifier *= 10 ** 9
|
||||
modifier = int(modifier) * (10 ** (28 - 9))
|
||||
|
||||
period /= (10 ** (28 - 9))
|
||||
period = int(period) * (10 ** (28 - 9))
|
||||
self.assertEqual(modifier, period)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds * 2)
|
||||
|
||||
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.redistributions(self.address, 2, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
redistribution = c.parse_redistributions(r)
|
||||
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_item(r)
|
||||
|
||||
# allow test code float rounding error to billionth
|
||||
modifier = (1 - (self.tax_level / 1000000)) ** ((self.period_seconds * 2) / 60)
|
||||
modifier *= 10 ** 9
|
||||
modifier = int(modifier) * (10 ** (28 - 9))
|
||||
|
||||
period /= (10 ** (28 - 9))
|
||||
period = int(period) * (10 ** (28 - 9))
|
||||
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)
|
||||
redistribution = self.contract.functions.redistributions(1).call()
|
||||
self.assertEqual(2, self.contract.functions.toRedistributionPeriod(redistribution).call())
|
||||
self.assertEqual(2, self.contract.functions.actualPeriod().call())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
108
python/tests/test_pure.py
Normal file
108
python/tests/test_pure.py
Normal file
@ -0,0 +1,108 @@
|
||||
# standard imports
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
|
||||
# third-party imports
|
||||
import web3
|
||||
import eth_tester
|
||||
import eth_abi
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
logging.getLogger('web3').setLevel(logging.WARNING)
|
||||
logging.getLogger('eth.vm').setLevel(logging.WARNING)
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
#BLOCKTIME = 5 # seconds
|
||||
TAX_LEVEL = int((10000 * 2) * (10 ** 32)) # 2%
|
||||
PERIOD = 10
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
contract = None
|
||||
|
||||
def setUp(self):
|
||||
eth_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
|
||||
'gas_limit': 9000000,
|
||||
})
|
||||
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.bin'), 'r')
|
||||
self.bytecode = f.read()
|
||||
f.close()
|
||||
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.json'), 'r')
|
||||
self.abi = json.load(f)
|
||||
f.close()
|
||||
|
||||
|
||||
backend = eth_tester.PyEVMBackend(eth_params)
|
||||
self.eth_tester = eth_tester.EthereumTester(backend)
|
||||
provider = web3.Web3.EthereumTesterProvider(self.eth_tester)
|
||||
self.w3 = web3.Web3(provider)
|
||||
self.sink_address = self.w3.eth.accounts[9]
|
||||
|
||||
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
|
||||
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
|
||||
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
|
||||
|
||||
self.start_block = self.w3.eth.blockNumber
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
@unittest.skip('this function has been removed from contract')
|
||||
def test_tax_period(self):
|
||||
t = self.contract.functions.taxLevel().call()
|
||||
logg.debug('taxlevel {}'.format(t))
|
||||
|
||||
a = self.contract.functions.toDemurrageAmount(1000000, 0).call()
|
||||
self.assertEqual(a, 1000000)
|
||||
|
||||
a = self.contract.functions.toDemurrageAmount(1000000, 1).call()
|
||||
self.assertEqual(a, 980000)
|
||||
|
||||
a = self.contract.functions.toDemurrageAmount(1000000, 2).call()
|
||||
self.assertEqual(a, 960400)
|
||||
|
||||
a = self.contract.functions.toDemurrageAmount(980000, 1).call()
|
||||
self.assertEqual(a, 960400)
|
||||
|
||||
|
||||
def test_fractional_state(self):
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
self.contract.functions.remainder(2, 1).call();
|
||||
|
||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
||||
remainder = self.contract.functions.remainder(0, 100001).call();
|
||||
|
||||
remainder = self.contract.functions.remainder(1, 2).call();
|
||||
self.assertEqual(remainder, 0);
|
||||
|
||||
whole = 5000001
|
||||
parts = 20000
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
remainder = self.contract.functions.remainder(parts, whole).call();
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
parts = 30000
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
remainder = self.contract.functions.remainder(parts, whole).call();
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
parts = 40001
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
remainder = self.contract.functions.remainder(parts, whole).call();
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -4,325 +4,198 @@ 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
|
||||
# third-party imports
|
||||
import web3
|
||||
import eth_tester
|
||||
import eth_abi
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
logging.getLogger('web3').setLevel(logging.WARNING)
|
||||
logging.getLogger('eth.vm').setLevel(logging.WARNING)
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
class TestRedistribution(TestDemurrageDefault):
|
||||
#BLOCKTIME = 5 # seconds
|
||||
TAX_LEVEL = 10000 * 2 # 2%
|
||||
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
|
||||
PERIOD = 1
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
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)
|
||||
contract = None
|
||||
|
||||
(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)
|
||||
def setUp(self):
|
||||
eth_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
|
||||
'gas_limit': 9000000,
|
||||
})
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds + 1)
|
||||
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
o = block_by_number(r)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['timestamp'], self.start_time + self.period_seconds)
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.bin'), 'r')
|
||||
self.bytecode = f.read()
|
||||
f.close()
|
||||
|
||||
(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)
|
||||
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.json'), 'r')
|
||||
self.abi = json.load(f)
|
||||
f.close()
|
||||
|
||||
(tx_hash, o) = c.apply_redistribution_on_account(self.address, self.accounts[1], self.accounts[1])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
backend = eth_tester.PyEVMBackend(eth_params)
|
||||
self.eth_tester = eth_tester.EthereumTester(backend)
|
||||
provider = web3.Web3.EthereumTesterProvider(self.eth_tester)
|
||||
self.w3 = web3.Web3(provider)
|
||||
self.sink_address = self.w3.eth.accounts[9]
|
||||
|
||||
balance = 0
|
||||
for i in range(3):
|
||||
o = c.balance_of(self.address, self.accounts[i+1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance_item = c.parse_balance_of(r)
|
||||
balance += balance_item
|
||||
logg.debug('balance {} {} total {}'.format(i, balance_item, balance))
|
||||
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
|
||||
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
|
||||
|
||||
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance_item = c.parse_balance_of(r)
|
||||
balance += balance_item
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
|
||||
|
||||
self.assertEqual(balance, 200000000)
|
||||
self.start_block = self.w3.eth.blockNumber
|
||||
b = self.w3.eth.getBlock(self.start_block)
|
||||
self.start_time = b['timestamp']
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
def debug_periods(self):
|
||||
pactual = self.contract.functions.actualPeriod().call()
|
||||
pstart = self.contract.functions.periodStart().call()
|
||||
pduration = self.contract.functions.periodDuration().call()
|
||||
blocknumber = self.w3.eth.blockNumber;
|
||||
logg.debug('actual {} start {} duration {} blocknumber {}'.format(pactual, pstart, pduration, blocknumber))
|
||||
|
||||
|
||||
# TODO: check receipt log outputs
|
||||
def test_redistribution_storage(self):
|
||||
redistribution = self.contract.functions.redistributions(0).call();
|
||||
self.assertEqual(redistribution.hex(), '000000000000000000000000f424000000000000000000000000000000000001')
|
||||
|
||||
self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact()
|
||||
self.contract.functions.mintTo(self.w3.eth.accounts[2], 1000000).transact()
|
||||
|
||||
external_address = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex())
|
||||
tx_hash = self.contract.functions.transfer(external_address, 1000000).transact({'from': self.w3.eth.accounts[2]})
|
||||
tx_hash = self.contract.functions.transfer(external_address, 999999).transact({'from': self.w3.eth.accounts[1]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
logg.debug('tx before {}'.format(r))
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
|
||||
redistribution = self.contract.functions.redistributions(0).call();
|
||||
self.assertEqual(redistribution.hex(), '000000000000000000000000f42400000000010000000000001e848000000001')
|
||||
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[0], 1000000).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
redistribution = self.contract.functions.redistributions(1).call()
|
||||
self.assertEqual(redistribution.hex(), '000000000000000000000000ef4200000000000000000000002dc6c000000002')
|
||||
|
||||
|
||||
# def test_debug_periods(self):
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
#
|
||||
# o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# pactual = c.parse_actual_period(r)
|
||||
#
|
||||
# o = c.period_start(self.address, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# pstart = c.parse_actual_period(r)
|
||||
#
|
||||
# o = c.period_duration(self.address, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# pduration = c.parse_actual_period(r)
|
||||
#
|
||||
# o = block_latest()
|
||||
# blocknumber = self.rpc.do(o)
|
||||
#
|
||||
# logg.debug('actual {} start {} duration {} blocknumber {}'.format(pactual, pstart, pduration, blocknumber))
|
||||
#
|
||||
#
|
||||
# # TODO: check receipt log outputs
|
||||
# def test_redistribution_storage(self):
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
# o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(strip_0x(r), '000000000000000000000000f424000000000000000000000000000000000001')
|
||||
#
|
||||
# (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1000000)
|
||||
# r = self.rpc.do(o)
|
||||
#
|
||||
# (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[2], 1000000)
|
||||
# r = self.rpc.do(o)
|
||||
#
|
||||
# external_address = to_checksum_address('0x' + os.urandom(20).hex())
|
||||
#
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[2], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
# (tx_hash, o) = c.transfer(self.address, self.accounts[2], external_address, 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.transfer(self.address, self.accounts[1], external_address, 999999)
|
||||
# r = self.rpc.do(o)
|
||||
#
|
||||
# self.backend.time_travel(self.start_time + self.period_seconds + 1)
|
||||
#
|
||||
# o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(strip_0x(r), '000000000000000000000000f42400000000010000000000001e848000000001')
|
||||
#
|
||||
# o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(strip_0x(r), '000000000000000000000000f42400000000010000000000001e848000000001')
|
||||
#
|
||||
#
|
||||
# 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], 1000000)
|
||||
# r = self.rpc.do(o)
|
||||
#
|
||||
# o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(strip_0x(r), '000000000000000000000000ef4200000000000000000000002dc6c000000002')
|
||||
#
|
||||
#
|
||||
# def test_redistribution_balance_on_zero_participants(self):
|
||||
# supply = self.default_supply
|
||||
#
|
||||
# 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], supply)
|
||||
# r = self.rpc.do(o)
|
||||
#
|
||||
# self.backend.time_travel(self.start_time + self.period_seconds + 1)
|
||||
# (tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
|
||||
# self.rpc.do(o)
|
||||
# o = receipt(tx_hash)
|
||||
# rcpt = self.rpc.do(o)
|
||||
# self.assertEqual(rcpt['status'], 1)
|
||||
#
|
||||
# (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.total_supply(self.address, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# total_supply = c.parse_total_supply(r)
|
||||
# sink_increment = int(total_supply * (self.tax_level / 1000000))
|
||||
# self.assertEqual(supply, total_supply)
|
||||
#
|
||||
# for l in rcpt['logs']:
|
||||
# if l['topics'][0] == '0xa0717e54e02bd9829db5e6e998aec0ae9de796b8d150a3cc46a92ab869697755': # event Decayed(uint256,uint256,uint256,uint256)
|
||||
# period = int.from_bytes(bytes.fromhex(strip_0x(l['topics'][1])), 'big')
|
||||
# self.assertEqual(period, 2)
|
||||
# b = bytes.fromhex(strip_0x(l['data']))
|
||||
# remainder = int.from_bytes(b, 'big')
|
||||
# self.assertEqual(remainder, int((1000000 - self.tax_level) * (10 ** 32)))
|
||||
#
|
||||
# o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# sink_balance = c.parse_balance_of(r)
|
||||
#
|
||||
# self.assertEqual(sink_balance, int(sink_increment * 0.98))
|
||||
# self.assertEqual(sink_balance, int(sink_increment * (1000000 - self.tax_level) / 1000000))
|
||||
#
|
||||
# o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# balance = c.parse_balance_of(r)
|
||||
# self.assertEqual(balance, supply - sink_increment)
|
||||
#
|
||||
#
|
||||
# def test_redistribution_two_of_ten(self):
|
||||
# mint_amount = 100000000
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
# z = 0
|
||||
# for i in range(10):
|
||||
# (tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[i], mint_amount)
|
||||
# self.rpc.do(o)
|
||||
# z += mint_amount
|
||||
#
|
||||
# o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# initial_balance = c.parse_balance_of(r)
|
||||
#
|
||||
# spend_amount = 1000000
|
||||
# external_address = to_checksum_address('0x' + os.urandom(20).hex())
|
||||
#
|
||||
# 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], external_address, spend_amount)
|
||||
# self.rpc.do(o)
|
||||
# o = receipt(tx_hash)
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(r['status'], 1)
|
||||
#
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[2], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
# (tx_hash, o) = c.transfer(self.address, self.accounts[2], external_address, spend_amount)
|
||||
# self.rpc.do(o)
|
||||
# o = receipt(tx_hash)
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(r['status'], 1)
|
||||
#
|
||||
# # No cheating!
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[3], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
# (tx_hash, o) = c.transfer(self.address, self.accounts[3], self.accounts[3], spend_amount)
|
||||
# self.rpc.do(o)
|
||||
# o = receipt(tx_hash)
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(r['status'], 1)
|
||||
#
|
||||
# # No cheapskating!
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[4], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
# (tx_hash, o) = c.transfer(self.address, self.accounts[4], external_address, spend_amount-1)
|
||||
# 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.apply_demurrage(self.address, self.accounts[4])
|
||||
# self.rpc.do(o)
|
||||
#
|
||||
# (tx_hash, o) = c.change_period(self.address, self.accounts[4])
|
||||
# self.rpc.do(o)
|
||||
#
|
||||
# o = c.balance_of(self.address, self.accounts[3], sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# bummer_balance = c.parse_balance_of(r)
|
||||
#
|
||||
# self.assertEqual(bummer_balance, mint_amount - (mint_amount * (self.tax_level / 1000000)))
|
||||
# logg.debug('bal {} '.format(bummer_balance))
|
||||
#
|
||||
# o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# bummer_balance = c.parse_balance_of(r)
|
||||
# spender_balance = mint_amount - spend_amount
|
||||
# spender_decayed_balance = int(spender_balance - (spender_balance * (self.tax_level / 1000000)))
|
||||
# self.assertEqual(bummer_balance, spender_decayed_balance)
|
||||
# logg.debug('bal {} '.format(bummer_balance))
|
||||
#
|
||||
# (tx_hash, o) = c.apply_redistribution_on_account(self.address, self.accounts[4], self.accounts[1])
|
||||
# self.rpc.do(o)
|
||||
# o = receipt(tx_hash)
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(r['status'], 1)
|
||||
#
|
||||
# (tx_hash, o) = c.apply_redistribution_on_account(self.address, self.accounts[4], self.accounts[2])
|
||||
# self.rpc.do(o)
|
||||
# o = receipt(tx_hash)
|
||||
# r = self.rpc.do(o)
|
||||
# self.assertEqual(r['status'], 1)
|
||||
#
|
||||
# o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# redistribution_data = c.parse_redistributions(r)
|
||||
# logg.debug('redist data {}'.format(redistribution_data))
|
||||
#
|
||||
# o = c.account_period(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# account_period_data = c.parse_account_period(r)
|
||||
# logg.debug('account period {}'.format(account_period_data))
|
||||
#
|
||||
# o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# actual_period = c.parse_actual_period(r)
|
||||
# logg.debug('period {}'.format(actual_period))
|
||||
#
|
||||
# redistribution = int((z / 2) * (self.tax_level / 1000000))
|
||||
# spender_new_base_balance = ((mint_amount - spend_amount) + redistribution)
|
||||
# spender_new_decayed_balance = int(spender_new_base_balance - (spender_new_base_balance * (self.tax_level / 1000000)))
|
||||
#
|
||||
# o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
# r = self.rpc.do(o)
|
||||
# spender_actual_balance = c.parse_balance_of(r)
|
||||
# logg.debug('rrr {} {}'.format(redistribution, spender_new_decayed_balance))
|
||||
#
|
||||
# self.assertEqual(spender_actual_balance, spender_new_decayed_balance)
|
||||
#
|
||||
def test_redistribution_balance_on_zero_participants(self):
|
||||
supply = 1000000000000
|
||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], supply).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
|
||||
tx_hash = self.contract.functions.applyDemurrage().transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
logg.debug('r {}'.format(r))
|
||||
self.assertEqual(r.status, 1)
|
||||
tx_hash = self.contract.functions.changePeriod().transact()
|
||||
rr = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
self.assertEqual(rr.status, 1)
|
||||
|
||||
redistribution = self.contract.functions.redistributions(0).call();
|
||||
supply = self.contract.functions.totalSupply().call()
|
||||
|
||||
sink_increment = int(supply * (TAX_LEVEL / 1000000))
|
||||
for l in r['logs']:
|
||||
if l.topics[0].hex() == '0xa0717e54e02bd9829db5e6e998aec0ae9de796b8d150a3cc46a92ab869697755': # event Decayed(uint256,uint256,uint256,uint256)
|
||||
period = int.from_bytes(l.topics[1], 'big')
|
||||
self.assertEqual(period, 2)
|
||||
b = bytes.fromhex(l.data[2:])
|
||||
remainder = int.from_bytes(b, 'big')
|
||||
self.assertEqual(remainder, int((1000000 - TAX_LEVEL) * (10 ** 32)))
|
||||
logg.debug('period {} remainder {}'.format(period, remainder))
|
||||
|
||||
sink_balance = self.contract.functions.balanceOf(self.sink_address).call()
|
||||
logg.debug('{} {}'.format(sink_increment, sink_balance))
|
||||
self.assertEqual(sink_balance, int(sink_increment * 0.98))
|
||||
self.assertEqual(sink_balance, int(sink_increment * (1000000 - TAX_LEVEL) / 1000000))
|
||||
|
||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
self.assertEqual(balance, supply - sink_increment)
|
||||
|
||||
|
||||
def test_redistribution_two_of_ten(self):
|
||||
mint_amount = 100000000
|
||||
z = 0
|
||||
for i in range(10):
|
||||
self.contract.functions.mintTo(self.w3.eth.accounts[i], mint_amount).transact()
|
||||
z += mint_amount
|
||||
|
||||
initial_balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
|
||||
spend_amount = 1000000
|
||||
external_address = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex())
|
||||
self.contract.functions.transfer(external_address, spend_amount).transact({'from': self.w3.eth.accounts[1]})
|
||||
tx_hash = self.contract.functions.transfer(external_address, spend_amount).transact({'from': self.w3.eth.accounts[2]})
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
# No cheating!
|
||||
self.contract.functions.transfer(self.w3.eth.accounts[3], spend_amount).transact({'from': self.w3.eth.accounts[3]})
|
||||
# No cheapskating!
|
||||
self.contract.functions.transfer(external_address, spend_amount-1).transact({'from': self.w3.eth.accounts[4]})
|
||||
|
||||
self.assertEqual(r.status, 1)
|
||||
|
||||
self.eth_tester.time_travel(self.start_time + 61)
|
||||
|
||||
self.contract.functions.applyDemurrage().transact()
|
||||
self.contract.functions.changePeriod().transact()
|
||||
|
||||
bummer_balance = self.contract.functions.balanceOf(self.w3.eth.accounts[3]).call()
|
||||
self.assertEqual(bummer_balance, mint_amount - (mint_amount * (TAX_LEVEL / 1000000)))
|
||||
logg.debug('bal {} '.format(bummer_balance))
|
||||
|
||||
bummer_balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
spender_balance = mint_amount - spend_amount
|
||||
spender_decayed_balance = int(spender_balance - (spender_balance * (TAX_LEVEL / 1000000)))
|
||||
self.assertEqual(bummer_balance, spender_decayed_balance)
|
||||
logg.debug('bal {} '.format(bummer_balance))
|
||||
|
||||
tx_hash = self.contract.functions.applyRedistributionOnAccount(self.w3.eth.accounts[1]).transact()
|
||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||
logg.debug('log {}'.format(r.logs))
|
||||
|
||||
self.contract.functions.applyRedistributionOnAccount(self.w3.eth.accounts[2]).transact()
|
||||
|
||||
redistribution_data = self.contract.functions.redistributions(0).call()
|
||||
logg.debug('redist data {}'.format(redistribution_data.hex()))
|
||||
|
||||
account_period_data = self.contract.functions.accountPeriod(self.w3.eth.accounts[1]).call()
|
||||
logg.debug('account period {}'.format(account_period_data))
|
||||
|
||||
actual_period = self.contract.functions.actualPeriod().call()
|
||||
logg.debug('period {}'.format(actual_period))
|
||||
|
||||
redistribution = int((z / 2) * (TAX_LEVEL / 1000000))
|
||||
spender_new_base_balance = ((mint_amount - spend_amount) + redistribution)
|
||||
spender_new_decayed_balance = int(spender_new_base_balance - (spender_new_base_balance * (TAX_LEVEL / 1000000)))
|
||||
|
||||
spender_actual_balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||
logg.debug('rrr {} {}'.format(redistribution, spender_new_decayed_balance))
|
||||
|
||||
self.assertEqual(spender_actual_balance, spender_new_decayed_balance)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
||||
|
@ -1,185 +0,0 @@
|
||||
# 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()
|
@ -1,198 +0,0 @@
|
||||
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 TestDemurrageUnit
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestRedistribution(TestDemurrageUnit):
|
||||
|
||||
|
||||
# TODO: move to "pure" test file when getdistribution is implemented in all contracts
|
||||
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**28)
|
||||
supply = self.default_supply
|
||||
|
||||
o = c.get_distribution(self.address, supply, demurrage, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
distribution = c.parse_get_distribution(r)
|
||||
expected_distribution = self.default_supply * (self.tax_level / 1000000)
|
||||
self.assert_within_lower(distribution, expected_distribution, 1000)
|
||||
|
||||
|
||||
def test_distribution_from_redistribution(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 / 100000)) * (10**28)
|
||||
|
||||
logg.debug('demurrage {}'.format(demurrage))
|
||||
supply = self.default_supply
|
||||
|
||||
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) / 100000
|
||||
logg.debug('distribution {} supply {}'.format(distribution, self.default_supply))
|
||||
self.assert_within_lower(distribution, expected_distribution, 1000)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
mint_amount = 100000000
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], mint_amount)
|
||||
self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
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)
|
||||
|
||||
self.assertEqual(balance, expected_balance)
|
||||
|
||||
|
||||
def test_single_step_multi(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
mint_amount = 100000000
|
||||
|
||||
for i in range(3):
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[i+1], mint_amount)
|
||||
self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
expected_balance = int(mint_amount - ((self.tax_level / 1000000) * mint_amount))
|
||||
|
||||
for i in range(3):
|
||||
o = c.balance_of(self.address, self.accounts[i+1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, expected_balance)
|
||||
|
||||
|
||||
def test_single_step_transfer(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
mint_amount = 100000000
|
||||
half_mint_amount = int(mint_amount / 2)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], mint_amount)
|
||||
self.rpc.do(o)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[2], mint_amount)
|
||||
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.transfer(self.address, self.accounts[1], self.accounts[3], half_mint_amount)
|
||||
self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[1])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
# check that we have crossed into new period, this will throw if not
|
||||
o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
demurrage_amount = int((self.tax_level / 1000000) * mint_amount)
|
||||
|
||||
expected_balance = mint_amount - demurrage_amount
|
||||
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, expected_balance)
|
||||
|
||||
half_demurrage_amount = int((self.tax_level / 1000000) * half_mint_amount)
|
||||
|
||||
expected_balance = half_mint_amount - half_demurrage_amount
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, expected_balance)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[3], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, expected_balance)
|
||||
|
||||
o = c.total_supply(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
supply = c.parse_total_supply(r)
|
||||
|
||||
o = c.redistributions(self.address, 0, sender_address=self.accounts[0])
|
||||
redistribution = self.rpc.do(o)
|
||||
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)
|
||||
logg.debug('\nrediistribution {}\ndemurrage {}\nsupply {}'.format(redistribution, demurrage, supply))
|
||||
|
||||
expected_balance = int(supply * (self.tax_level / 1000000))
|
||||
expected_balance_tolerance = 1
|
||||
|
||||
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.assert_within_lower(balance, expected_balance, 1000)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,70 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.error import JSONRPCException
|
||||
import eth_tester
|
||||
|
||||
# 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 Test(TestDemurrageDefault):
|
||||
|
||||
def test_fractional_state(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
with self.assertRaises(JSONRPCException):
|
||||
o = c.remainder(self.address, 2, 1, sender_address=self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
with self.assertRaises(JSONRPCException):
|
||||
o = c.remainder(self.address, 0, 100001, sender_address=self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
o = c.remainder(self.address, 1, 2, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, 0);
|
||||
|
||||
whole = 5000001
|
||||
parts = 20000
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
o = c.remainder(self.address, parts, whole, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
parts = 30000
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
o = c.remainder(self.address, parts, whole, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
parts = 40001
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
o = c.remainder(self.address, parts, whole, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,97 +0,0 @@
|
||||
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
|
||||
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 TestDemurrageSingle
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestRedistributionSingle(TestDemurrageSingle):
|
||||
|
||||
|
||||
def test_single_even_if_multiple(self):
|
||||
|
||||
mint_amount = 100000000
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
for i in range(3):
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[i+1], mint_amount)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
external_address = to_checksum_address('0x' + os.urandom(20).hex())
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[2], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer(self.address, self.accounts[2], external_address, int(mint_amount) * 0.1)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[3], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer(self.address, self.accounts[3], external_address, int(mint_amount) * 0.2)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds + 1)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[3])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
rcpt = self.rpc.do(o)
|
||||
self.assertEqual(rcpt['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[3])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
tax_modifier = (1 - (self.tax_level / 1000000)) ** 10
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, int(mint_amount * tax_modifier))
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
base_amount = mint_amount - int(mint_amount * 0.1)
|
||||
self.assertEqual(balance, int(base_amount * tax_modifier)) #(base_amount - (base_amount * (self.tax_level / 1000000))))
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[3], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
base_amount = mint_amount - int(mint_amount * 0.2)
|
||||
self.assertEqual(balance, int(base_amount * tax_modifier)) #(base_amount - (base_amount * (self.tax_level / 1000000))))
|
||||
|
||||
o = c.total_supply(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
new_supply = c.parse_total_supply(r)
|
||||
|
||||
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
expected_balance = new_supply - (new_supply * tax_modifier)
|
||||
self.assert_within_lower(balance, expected_balance, 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,633 +0,0 @@
|
||||
pragma solidity > 0.6.11;
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
contract DemurrageTokenMultiCap {
|
||||
|
||||
// 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)
|
||||
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
|
||||
|
||||
// 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;
|
||||
|
||||
// Implements EIP172
|
||||
address public owner;
|
||||
|
||||
address newOwner;
|
||||
|
||||
// Implements ERC20
|
||||
string public name;
|
||||
|
||||
// Implements ERC20
|
||||
string public symbol;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public decimals;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public totalSupply;
|
||||
|
||||
// Maximum amount of tokens that can be minted
|
||||
uint256 public supplyCap;
|
||||
|
||||
// Minimum amount of (demurraged) tokens an account must spend to participate in redistribution for a particular period
|
||||
uint256 public minimumParticipantSpend;
|
||||
|
||||
// 128 bit resolution of the demurrage divisor
|
||||
// (this constant x 1000000 is contained within 128 bits)
|
||||
uint256 constant ppmDivider = 100000000000000000000000000000000;
|
||||
|
||||
// demurrage decimal width; 38 places
|
||||
uint256 public immutable resolutionFactor = ppmDivider * 1000000;
|
||||
|
||||
// Timestamp of start of periods (time which contract constructor was called)
|
||||
uint256 public immutable periodStart;
|
||||
|
||||
// Duration of a single redistribution period in seconds
|
||||
uint256 public immutable periodDuration;
|
||||
|
||||
// Demurrage in ppm per minute
|
||||
uint256 public immutable taxLevel;
|
||||
|
||||
// Addresses allowed to mint new tokens
|
||||
mapping (address => bool) minter;
|
||||
|
||||
// Storage for ERC20 approve/transferFrom methods
|
||||
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
|
||||
|
||||
// Address to send unallocated redistribution tokens
|
||||
address sinkAddress;
|
||||
|
||||
// Implements ERC20
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||
|
||||
// Implements ERC20
|
||||
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
|
||||
|
||||
// New tokens minted
|
||||
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
|
||||
|
||||
// New demurrage cache milestone calculated
|
||||
event Decayed(uint256 indexed _period, uint256 indexed _periodCount, uint256 indexed _oldAmount, uint256 _newAmount);
|
||||
|
||||
// When a new period threshold has been crossed
|
||||
event Period(uint256 _period);
|
||||
|
||||
// Redistribution applied on a single eligible account
|
||||
event Redistribution(address indexed _account, uint256 indexed _period, uint256 _value);
|
||||
|
||||
// Temporary event used in development, will be removed on prod
|
||||
event Debug(bytes32 _foo);
|
||||
|
||||
// EIP173
|
||||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
|
||||
|
||||
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress, uint256 _supplyCap) public {
|
||||
// ACL setup
|
||||
owner = msg.sender;
|
||||
minter[owner] = true;
|
||||
|
||||
// ERC20 setup
|
||||
name = _name;
|
||||
symbol = _symbol;
|
||||
decimals = _decimals;
|
||||
|
||||
// Demurrage setup
|
||||
demurrageTimestamp = block.timestamp;
|
||||
periodStart = demurrageTimestamp;
|
||||
periodDuration = _periodMinutes * 60;
|
||||
demurrageAmount = uint128(ppmDivider * 1000000); // Represents 38 decimal places
|
||||
//demurragePeriod = 1;
|
||||
taxLevel = _taxLevelMinute; // Represents 38 decimal places
|
||||
bytes32 initialRedistribution = toRedistribution(0, 1000000, 0, 1);
|
||||
redistributions.push(initialRedistribution);
|
||||
|
||||
// Misc settings
|
||||
supplyCap = _supplyCap;
|
||||
sinkAddress = _defaultSinkAddress;
|
||||
minimumParticipantSpend = 10 ** uint256(_decimals);
|
||||
}
|
||||
|
||||
// Given address will be allowed to call the mintTo() function
|
||||
function addMinter(address _minter) public returns (bool) {
|
||||
require(msg.sender == owner);
|
||||
minter[_minter] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Given address will no longer be allowed to call the mintTo() function
|
||||
function removeMinter(address _minter) public returns (bool) {
|
||||
require(msg.sender == owner || _minter == msg.sender);
|
||||
minter[_minter] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Implements ERC20
|
||||
function balanceOf(address _account) public view returns (uint256) {
|
||||
uint256 baseBalance;
|
||||
uint256 currentDemurragedAmount;
|
||||
uint256 periodCount;
|
||||
|
||||
baseBalance = baseBalanceOf(_account);
|
||||
|
||||
//periodCount = actualPeriod() - demurragePeriod;
|
||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||
|
||||
currentDemurragedAmount = uint128(decayBy(demurrageAmount, periodCount));
|
||||
|
||||
return (baseBalance * currentDemurragedAmount) / (ppmDivider * 1000000);
|
||||
}
|
||||
|
||||
/// Balance unmodified by demurrage
|
||||
function baseBalanceOf(address _account) public view returns (uint256) {
|
||||
return uint256(account[_account]) & maskAccountValue;
|
||||
}
|
||||
|
||||
/// Increases base balance for a single account
|
||||
function increaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
|
||||
uint256 oldBalance;
|
||||
uint256 newBalance;
|
||||
uint256 workAccount;
|
||||
|
||||
workAccount = uint256(account[_account]);
|
||||
|
||||
if (_delta == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Decreases base balance for a single account
|
||||
function decreaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
|
||||
uint256 oldBalance;
|
||||
uint256 newBalance;
|
||||
uint256 workAccount;
|
||||
|
||||
workAccount = uint256(account[_account]);
|
||||
|
||||
if (_delta == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oldBalance = baseBalanceOf(_account);
|
||||
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
|
||||
newBalance = oldBalance - _delta;
|
||||
workAccount &= (~maskAccountValue);
|
||||
workAccount |= (newBalance & maskAccountValue);
|
||||
account[_account] = bytes32(workAccount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Creates new tokens out of thin air, and allocates them to the given address
|
||||
// Triggers tax
|
||||
function mintTo(address _beneficiary, uint256 _amount) external returns (bool) {
|
||||
uint256 baseAmount;
|
||||
|
||||
require(minter[msg.sender]);
|
||||
require(_amount + totalSupply <= supplyCap);
|
||||
|
||||
changePeriod();
|
||||
baseAmount = toBaseAmount(_amount);
|
||||
totalSupply += _amount;
|
||||
increaseBaseBalance(_beneficiary, baseAmount);
|
||||
emit Mint(msg.sender, _beneficiary, _amount);
|
||||
saveRedistributionSupply();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deserializes the redistribution word
|
||||
// uint1(isFractional) | 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;
|
||||
}
|
||||
|
||||
// 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) & 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 demurrage modifier 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
currentRedistribution = uint256(redistributions[redistributions.length-1]);
|
||||
currentRedistribution &= (~maskRedistributionValue);
|
||||
currentRedistribution |= (totalSupply << shiftRedistributionValue);
|
||||
|
||||
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the demurrage period of the current block number
|
||||
function actualPeriod() public view returns (uint128) {
|
||||
return uint128((block.timestamp - periodStart) / periodDuration + 1);
|
||||
}
|
||||
|
||||
// Add an entered demurrage period to the redistribution array
|
||||
function checkPeriod() private view returns (bytes32) {
|
||||
bytes32 lastRedistribution;
|
||||
uint256 currentPeriod;
|
||||
|
||||
lastRedistribution = redistributions[redistributions.length-1];
|
||||
currentPeriod = this.actualPeriod();
|
||||
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
|
||||
return bytes32(0x00);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Called in the edge case where participant number is 0. It will override the participant count to 1.
|
||||
// Returns the remainder sent to the sink address
|
||||
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
|
||||
uint256 redistributionSupply;
|
||||
uint256 redistributionPeriod;
|
||||
uint256 unit;
|
||||
uint256 truncatedResult;
|
||||
|
||||
redistributionSupply = toRedistributionSupply(_redistribution);
|
||||
|
||||
unit = (redistributionSupply * taxLevel) / 1000000;
|
||||
truncatedResult = (unit * 1000000) / taxLevel;
|
||||
|
||||
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 the time delta in whole minutes passed between given timestamp and current timestamp
|
||||
function getMinutesDelta(uint256 _lastTimestamp) public view returns (uint256) {
|
||||
return (block.timestamp - _lastTimestamp) / 60;
|
||||
}
|
||||
|
||||
// Calculate and cache the demurrage value corresponding to the (period of the) time of the method call
|
||||
function applyDemurrage() public returns (bool) {
|
||||
//uint128 epochPeriodCount;
|
||||
uint256 periodCount;
|
||||
uint256 lastDemurrageAmount;
|
||||
uint256 newDemurrageAmount;
|
||||
|
||||
//epochPeriodCount = actualPeriod();
|
||||
//periodCount = epochPeriodCount - demurragePeriod;
|
||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||
if (periodCount == 0) {
|
||||
return false;
|
||||
}
|
||||
lastDemurrageAmount = demurrageAmount;
|
||||
demurrageAmount = uint128(decayBy(lastDemurrageAmount, periodCount));
|
||||
//demurragePeriod = epochPeriodCount;
|
||||
demurrageTimestamp = demurrageTimestamp + (periodCount * 60);
|
||||
//emit Decayed(epochPeriodCount, periodCount, lastDemurrageAmount, demurrageAmount);
|
||||
emit Decayed(demurrageTimestamp, periodCount, lastDemurrageAmount, demurrageAmount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return timestamp of start of period threshold
|
||||
function getPeriodTimeDelta(uint256 _periodCount) public view returns (uint256) {
|
||||
return periodStart + (_periodCount * periodDuration);
|
||||
}
|
||||
|
||||
// Amount of demurrage cycles inbetween the current timestamp and the given target time
|
||||
function demurrageCycles(uint256 _target) public view returns (uint256) {
|
||||
return (block.timestamp - _target) / 60;
|
||||
}
|
||||
|
||||
// Recalculate the demurrage modifier for the new period
|
||||
// After this, all REPORTED balances will have been reduced by the corresponding ratio (but the effecive totalsupply stays the same)
|
||||
function changePeriod() public returns (bool) {
|
||||
bytes32 currentRedistribution;
|
||||
bytes32 nextRedistribution;
|
||||
uint256 currentPeriod;
|
||||
uint256 currentParticipants;
|
||||
uint256 currentRemainder;
|
||||
uint256 currentDemurrageAmount;
|
||||
uint256 nextRedistributionDemurrage;
|
||||
uint256 demurrageCounts;
|
||||
uint256 periodTimestamp;
|
||||
uint256 nextPeriod;
|
||||
|
||||
applyDemurrage();
|
||||
|
||||
currentRedistribution = checkPeriod();
|
||||
if (currentRedistribution == bytes32(0x00)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
currentPeriod = toRedistributionPeriod(currentRedistribution);
|
||||
nextPeriod = currentPeriod + 1;
|
||||
periodTimestamp = getPeriodTimeDelta(currentPeriod);
|
||||
|
||||
//applyDemurrage();
|
||||
currentDemurrageAmount = demurrageAmount;
|
||||
|
||||
demurrageCounts = demurrageCycles(periodTimestamp);
|
||||
if (demurrageCounts > 0) {
|
||||
nextRedistributionDemurrage = growBy(currentDemurrageAmount, demurrageCounts) / ppmDivider;
|
||||
} else {
|
||||
nextRedistributionDemurrage = currentDemurrageAmount / ppmDivider;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
emit Period(nextPeriod);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 = 1000000;
|
||||
truncatedTaxLevel = taxLevel / ppmDivider;
|
||||
|
||||
for (uint256 i = 0; i < _period; i++) {
|
||||
valueFactor = valueFactor + ((valueFactor * truncatedTaxLevel) / 1000000);
|
||||
}
|
||||
return (valueFactor * _value) / 1000000;
|
||||
}
|
||||
|
||||
// Calculate a value reduced by demurrage by the given period
|
||||
// TODO: higher precision if possible
|
||||
function decayBy(uint256 _value, uint256 _period) public view returns (uint256) {
|
||||
uint256 valueFactor;
|
||||
uint256 truncatedTaxLevel;
|
||||
|
||||
valueFactor = 1000000;
|
||||
truncatedTaxLevel = taxLevel / ppmDivider;
|
||||
|
||||
for (uint256 i = 0; i < _period; i++) {
|
||||
valueFactor = valueFactor - ((valueFactor * truncatedTaxLevel) / 1000000);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function approve(address _spender, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
|
||||
changePeriod();
|
||||
applyRedistributionOnAccount(msg.sender);
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
allowance[msg.sender][_spender] += baseValue;
|
||||
emit Approval(msg.sender, _spender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function transfer(address _to, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
bool result;
|
||||
|
||||
changePeriod();
|
||||
applyRedistributionOnAccount(msg.sender);
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
result = transferBase(msg.sender, _to, baseValue);
|
||||
emit Transfer(msg.sender, _to, _value);
|
||||
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);
|
||||
|
||||
result = transferBase(_from, _to, baseValue);
|
||||
emit Transfer(_from, _to, _value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ERC20 transfer backend for transfer, transferFrom
|
||||
function transferBase(address _from, address _to, uint256 _value) private returns (bool) {
|
||||
uint256 period;
|
||||
|
||||
decreaseBaseBalance(_from, _value);
|
||||
increaseBaseBalance(_to, _value);
|
||||
|
||||
period = actualPeriod();
|
||||
if (_value >= minimumParticipantSpend && accountPeriod(_from) != period && _from != _to) {
|
||||
registerAccountPeriod(_from, period);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements EIP173
|
||||
function transferOwnership(address _newOwner) public returns (bool) {
|
||||
require(msg.sender == owner);
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
|
||||
// Implements OwnedAccepter
|
||||
function acceptOwnership() public returns (bool) {
|
||||
address oldOwner;
|
||||
|
||||
require(msg.sender == newOwner);
|
||||
oldOwner = owner;
|
||||
owner = newOwner;
|
||||
newOwner = address(0);
|
||||
emit OwnershipTransferred(oldOwner, owner);
|
||||
}
|
||||
|
||||
// Implements EIP165
|
||||
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
||||
if (_sum == 0xc6bb4b70) { // ERC20
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x449a52f8) { // Minter
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x01ffc9a7) { // EIP165
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x9493f8b2) { // EIP173
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x37a47be4) { // OwnedAccepter
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,517 +0,0 @@
|
||||
pragma solidity > 0.6.11;
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
contract DemurrageTokenSingleCap {
|
||||
|
||||
// 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
|
||||
|
||||
// Account balances
|
||||
mapping (address => uint256) account;
|
||||
|
||||
// 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;
|
||||
|
||||
// Implements EIP172
|
||||
address public owner;
|
||||
|
||||
address newOwner;
|
||||
|
||||
// Implements ERC20
|
||||
string public name;
|
||||
|
||||
// Implements ERC20
|
||||
string public symbol;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public decimals;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public totalSupply;
|
||||
|
||||
// Maximum amount of tokens that can be minted
|
||||
uint256 public supplyCap;
|
||||
|
||||
// Minimum amount of (demurraged) tokens an account must spend to participate in redistribution for a particular period
|
||||
uint256 public minimumParticipantSpend;
|
||||
|
||||
// 128 bit resolution of the demurrage divisor
|
||||
// (this constant x 1000000 is contained within 128 bits)
|
||||
uint256 constant nanoDivider = 100000000000000000000000000; // now nanodivider, 6 zeros less
|
||||
|
||||
// remaining decimal positions of nanoDivider to reach 38, equals precision in growth and decay
|
||||
uint256 constant growthResolutionFactor = 1000000000000;
|
||||
|
||||
// demurrage decimal width; 38 places
|
||||
uint256 public immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
||||
|
||||
// Timestamp of start of periods (time which contract constructor was called)
|
||||
uint256 public immutable periodStart;
|
||||
|
||||
// Duration of a single redistribution period in seconds
|
||||
uint256 public immutable periodDuration;
|
||||
|
||||
// Demurrage in ppm per minute
|
||||
uint256 public immutable taxLevel;
|
||||
|
||||
// Addresses allowed to mint new tokens
|
||||
mapping (address => bool) minter;
|
||||
|
||||
// Storage for ERC20 approve/transferFrom methods
|
||||
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
|
||||
|
||||
// Address to send unallocated redistribution tokens
|
||||
address sinkAddress;
|
||||
|
||||
// Implements ERC20
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||
|
||||
// Implements ERC20
|
||||
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
|
||||
|
||||
// New tokens minted
|
||||
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
|
||||
|
||||
// New demurrage cache milestone calculated
|
||||
event Decayed(uint256 indexed _period, uint256 indexed _periodCount, uint256 indexed _oldAmount, uint256 _newAmount);
|
||||
|
||||
// When a new period threshold has been crossed
|
||||
event Period(uint256 _period);
|
||||
|
||||
// Redistribution applied on a single eligible account
|
||||
event Redistribution(address indexed _account, uint256 indexed _period, uint256 _value);
|
||||
|
||||
// Temporary event used in development, will be removed on prod
|
||||
event Debug(bytes32 _foo);
|
||||
|
||||
// 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, uint256 _supplyCap) public {
|
||||
// ACL setup
|
||||
owner = msg.sender;
|
||||
minter[owner] = true;
|
||||
|
||||
// ERC20 setup
|
||||
name = _name;
|
||||
symbol = _symbol;
|
||||
decimals = _decimals;
|
||||
|
||||
// Demurrage setup
|
||||
demurrageTimestamp = block.timestamp;
|
||||
periodStart = demurrageTimestamp;
|
||||
periodDuration = _periodMinutes * 60;
|
||||
//demurrageAmount = 100000000000000000000000000000000000000 - _taxLevelMinute; // Represents 38 decimal places, same as resolutionFactor
|
||||
//demurrageAmount = 100000000000000000000000000000000000000;
|
||||
demurrageAmount = 10000000000000000000000000000;
|
||||
//demurragePeriod = 1;
|
||||
taxLevel = _taxLevelMinute; // Represents 38 decimal places
|
||||
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
|
||||
redistributions.push(initialRedistribution);
|
||||
|
||||
// Misc settings
|
||||
supplyCap = _supplyCap;
|
||||
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
|
||||
function addMinter(address _minter) public returns (bool) {
|
||||
require(msg.sender == owner);
|
||||
minter[_minter] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Given address will no longer be allowed to call the mintTo() function
|
||||
function removeMinter(address _minter) public returns (bool) {
|
||||
require(msg.sender == owner || _minter == msg.sender);
|
||||
minter[_minter] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Implements ERC20
|
||||
function balanceOf(address _account) public view returns (uint256) {
|
||||
uint256 baseBalance;
|
||||
uint256 currentDemurragedAmount;
|
||||
uint256 periodCount;
|
||||
|
||||
baseBalance = baseBalanceOf(_account);
|
||||
|
||||
//periodCount = actualPeriod() - demurragePeriod;
|
||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||
|
||||
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
|
||||
|
||||
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
|
||||
}
|
||||
|
||||
/// Balance unmodified by demurrage
|
||||
function baseBalanceOf(address _account) public view returns (uint256) {
|
||||
return account[_account];
|
||||
}
|
||||
|
||||
/// Increases base balance for a single account
|
||||
function increaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
|
||||
uint256 oldBalance;
|
||||
uint256 newBalance;
|
||||
uint256 workAccount;
|
||||
|
||||
workAccount = uint256(account[_account]);
|
||||
|
||||
if (_delta == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oldBalance = baseBalanceOf(_account);
|
||||
account[_account] = oldBalance + _delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Decreases base balance for a single account
|
||||
function decreaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
|
||||
uint256 oldBalance;
|
||||
uint256 newBalance;
|
||||
uint256 workAccount;
|
||||
|
||||
workAccount = uint256(account[_account]);
|
||||
|
||||
if (_delta == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oldBalance = baseBalanceOf(_account);
|
||||
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
|
||||
account[_account] = oldBalance - _delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Creates new tokens out of thin air, and allocates them to the given address
|
||||
// Triggers tax
|
||||
function mintTo(address _beneficiary, uint256 _amount) external returns (bool) {
|
||||
uint256 baseAmount;
|
||||
|
||||
require(minter[msg.sender], 'ERR_ACCESS');
|
||||
require(_amount + totalSupply <= supplyCap, 'ERR_CAP');
|
||||
|
||||
changePeriod();
|
||||
baseAmount = toBaseAmount(_amount);
|
||||
totalSupply += _amount;
|
||||
increaseBaseBalance(_beneficiary, baseAmount);
|
||||
emit Mint(msg.sender, _beneficiary, _amount);
|
||||
saveRedistributionSupply();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
|
||||
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
|
||||
redistribution |= bytes32(_period & maskRedistributionPeriod);
|
||||
return redistribution;
|
||||
}
|
||||
|
||||
// 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) & 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;
|
||||
}
|
||||
|
||||
// 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
|
||||
function saveRedistributionSupply() private returns (bool) {
|
||||
uint256 currentRedistribution;
|
||||
uint256 grownSupply;
|
||||
|
||||
//grownSupply = growBy(totalSupply, 1);
|
||||
grownSupply = totalSupply;
|
||||
currentRedistribution = uint256(redistributions[redistributions.length-1]);
|
||||
currentRedistribution &= (~maskRedistributionValue);
|
||||
currentRedistribution |= (grownSupply << shiftRedistributionValue);
|
||||
|
||||
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the demurrage period of the current block number
|
||||
function actualPeriod() public view returns (uint128) {
|
||||
return uint128((block.timestamp - periodStart) / periodDuration + 1);
|
||||
}
|
||||
|
||||
// Add an entered demurrage period to the redistribution array
|
||||
function checkPeriod() private view returns (bytes32) {
|
||||
bytes32 lastRedistribution;
|
||||
uint256 currentPeriod;
|
||||
|
||||
lastRedistribution = redistributions[redistributions.length-1];
|
||||
currentPeriod = this.actualPeriod();
|
||||
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
|
||||
return bytes32(0x00);
|
||||
}
|
||||
return lastRedistribution;
|
||||
}
|
||||
|
||||
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
|
||||
uint256 difference;
|
||||
|
||||
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000)); //(nanoDivider - ((resolutionFactor - _demurrageAmount) / nanoDivider));
|
||||
return difference / resolutionFactor;
|
||||
}
|
||||
|
||||
function getDistributionFromRedistribution(bytes32 _redistribution) public returns (uint256) {
|
||||
uint256 redistributionSupply;
|
||||
uint256 redistributionDemurrage;
|
||||
|
||||
redistributionSupply = toRedistributionSupply(_redistribution);
|
||||
redistributionDemurrage = toRedistributionDemurrageModifier(_redistribution);
|
||||
return getDistribution(redistributionSupply, redistributionDemurrage);
|
||||
}
|
||||
|
||||
// Returns the amount sent to the sink address
|
||||
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
|
||||
uint256 unit;
|
||||
|
||||
unit = getDistributionFromRedistribution(_redistribution);
|
||||
increaseBaseBalance(sinkAddress, toBaseAmount(unit));
|
||||
return unit;
|
||||
}
|
||||
|
||||
// Calculate the time delta in whole minutes passed between given timestamp and current timestamp
|
||||
function getMinutesDelta(uint256 _lastTimestamp) public view returns (uint256) {
|
||||
return (block.timestamp - _lastTimestamp) / 60;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
//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);
|
||||
emit Decayed(demurrageTimestamp, periodCount, lastDemurrageAmount, demurrageAmount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return timestamp of start of period threshold
|
||||
function getPeriodTimeDelta(uint256 _periodCount) public view returns (uint256) {
|
||||
return periodStart + (_periodCount * periodDuration);
|
||||
}
|
||||
|
||||
// Amount of demurrage cycles inbetween the current timestamp and the given target time
|
||||
function demurrageCycles(uint256 _target) public view returns (uint256) {
|
||||
return (block.timestamp - _target) / 60;
|
||||
}
|
||||
|
||||
// Recalculate the demurrage modifier for the new period
|
||||
function changePeriod() public returns (bool) {
|
||||
bytes32 currentRedistribution;
|
||||
bytes32 nextRedistribution;
|
||||
uint256 currentPeriod;
|
||||
uint256 currentDemurrageAmount;
|
||||
uint256 nextRedistributionDemurrage;
|
||||
uint256 demurrageCounts;
|
||||
uint256 periodTimestamp;
|
||||
uint256 nextPeriod;
|
||||
|
||||
applyDemurrage();
|
||||
currentRedistribution = checkPeriod();
|
||||
if (currentRedistribution == bytes32(0x00)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
redistributions.push(nextRedistribution);
|
||||
|
||||
applyDefaultRedistribution(nextRedistribution);
|
||||
emit Period(nextPeriod);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Calculate a value reduced by demurrage by the given period
|
||||
function decayBy(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;
|
||||
}
|
||||
|
||||
// Inflates the given amount according to the current demurrage modifier
|
||||
function toBaseAmount(uint256 _value) public view returns (uint256) {
|
||||
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function approve(address _spender, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
|
||||
changePeriod();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
allowance[msg.sender][_spender] += baseValue;
|
||||
emit Approval(msg.sender, _spender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function transfer(address _to, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
bool result;
|
||||
|
||||
changePeriod();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
result = transferBase(msg.sender, _to, baseValue);
|
||||
emit Transfer(msg.sender, _to, _value);
|
||||
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();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
require(allowance[_from][msg.sender] >= baseValue);
|
||||
|
||||
result = transferBase(_from, _to, baseValue);
|
||||
emit Transfer(_from, _to, _value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ERC20 transfer backend for transfer, transferFrom
|
||||
function transferBase(address _from, address _to, uint256 _value) private returns (bool) {
|
||||
uint256 period;
|
||||
|
||||
decreaseBaseBalance(_from, _value);
|
||||
increaseBaseBalance(_to, _value);
|
||||
|
||||
//period = actualPeriod();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements EIP173
|
||||
function transferOwnership(address _newOwner) public returns (bool) {
|
||||
require(msg.sender == owner);
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
|
||||
// Implements OwnedAccepter
|
||||
function acceptOwnership() public returns (bool) {
|
||||
address oldOwner;
|
||||
|
||||
require(msg.sender == newOwner);
|
||||
oldOwner = owner;
|
||||
owner = newOwner;
|
||||
newOwner = address(0);
|
||||
emit OwnershipTransferred(oldOwner, owner);
|
||||
}
|
||||
|
||||
// Implements EIP165
|
||||
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
||||
if (_sum == 0xc6bb4b70) { // ERC20
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x449a52f8) { // Minter
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x01ffc9a7) { // EIP165
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x9493f8b2) { // EIP173
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x37a47be4) { // OwnedAccepter
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,508 +0,0 @@
|
||||
pragma solidity > 0.6.11;
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
contract DemurrageTokenSingleCap {
|
||||
|
||||
// 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
|
||||
|
||||
// Account balances
|
||||
mapping (address => uint256) account;
|
||||
|
||||
// Cached demurrage amount, ppm with 38 digit resolution
|
||||
uint128 public demurrageAmount;
|
||||
|
||||
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
|
||||
uint256 public demurrageTimestamp;
|
||||
|
||||
// Implements EIP172
|
||||
address public owner;
|
||||
|
||||
address newOwner;
|
||||
|
||||
// Implements ERC20
|
||||
string public name;
|
||||
|
||||
// Implements ERC20
|
||||
string public symbol;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public decimals;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public totalSupply;
|
||||
|
||||
// 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)
|
||||
uint256 constant nanoDivider = 100000000000000000000000000; // now nanodivider, 6 zeros less
|
||||
|
||||
// remaining decimal positions of nanoDivider to reach 38, equals precision in growth and decay
|
||||
uint256 constant growthResolutionFactor = 1000000000000;
|
||||
|
||||
// demurrage decimal width; 38 places
|
||||
uint256 public immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
||||
|
||||
// Timestamp of start of periods (time which contract constructor was called)
|
||||
uint256 public immutable periodStart;
|
||||
|
||||
// Duration of a single redistribution period in seconds
|
||||
uint256 public immutable periodDuration;
|
||||
|
||||
// Demurrage in ppm per minute
|
||||
uint256 public immutable taxLevel;
|
||||
|
||||
// Addresses allowed to mint new tokens
|
||||
mapping (address => bool) minter;
|
||||
|
||||
// Storage for ERC20 approve/transferFrom methods
|
||||
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
|
||||
|
||||
// Address to send unallocated redistribution tokens
|
||||
address public sinkAddress;
|
||||
|
||||
// Implements ERC20
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||
|
||||
// Implements ERC20
|
||||
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
|
||||
|
||||
// New tokens minted
|
||||
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
|
||||
|
||||
// New demurrage cache milestone calculated
|
||||
event Decayed(uint256 indexed _period, uint256 indexed _periodCount, uint256 indexed _oldAmount, uint256 _newAmount);
|
||||
|
||||
// When a new period threshold has been crossed
|
||||
event Period(uint256 _period);
|
||||
|
||||
// Redistribution applied on a single eligible account
|
||||
event Redistribution(address indexed _account, uint256 indexed _period, uint256 _value);
|
||||
|
||||
// Temporary event used in development, will be removed on prod
|
||||
event Debug(bytes32 _foo);
|
||||
|
||||
// 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 {
|
||||
// ACL setup
|
||||
owner = msg.sender;
|
||||
minter[owner] = true;
|
||||
|
||||
// ERC20 setup
|
||||
name = _name;
|
||||
symbol = _symbol;
|
||||
decimals = _decimals;
|
||||
|
||||
// Demurrage setup
|
||||
demurrageTimestamp = block.timestamp;
|
||||
periodStart = demurrageTimestamp;
|
||||
periodDuration = _periodMinutes * 60;
|
||||
demurrageAmount = uint128(nanoDivider) * 100;
|
||||
taxLevel = _taxLevelMinute; // Represents 38 decimal places
|
||||
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
|
||||
redistributions.push(initialRedistribution);
|
||||
|
||||
// Misc settings
|
||||
sinkAddress = _defaultSinkAddress;
|
||||
}
|
||||
|
||||
|
||||
// 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);
|
||||
minter[_minter] = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Given address will no longer be allowed to call the mintTo() function
|
||||
function removeMinter(address _minter) public returns (bool) {
|
||||
require(msg.sender == owner || _minter == msg.sender);
|
||||
minter[_minter] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Implements ERC20
|
||||
function balanceOf(address _account) public view returns (uint256) {
|
||||
uint256 baseBalance;
|
||||
uint256 currentDemurragedAmount;
|
||||
uint256 periodCount;
|
||||
|
||||
baseBalance = baseBalanceOf(_account);
|
||||
|
||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||
|
||||
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
|
||||
|
||||
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
|
||||
}
|
||||
|
||||
/// Balance unmodified by demurrage
|
||||
function baseBalanceOf(address _account) public view returns (uint256) {
|
||||
return account[_account];
|
||||
}
|
||||
|
||||
/// Increases base balance for a single account
|
||||
function increaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
|
||||
uint256 oldBalance;
|
||||
uint256 newBalance;
|
||||
uint256 workAccount;
|
||||
|
||||
workAccount = uint256(account[_account]);
|
||||
|
||||
if (_delta == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oldBalance = baseBalanceOf(_account);
|
||||
account[_account] = oldBalance + _delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Decreases base balance for a single account
|
||||
function decreaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
|
||||
uint256 oldBalance;
|
||||
uint256 newBalance;
|
||||
uint256 workAccount;
|
||||
|
||||
workAccount = uint256(account[_account]);
|
||||
|
||||
if (_delta == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
oldBalance = baseBalanceOf(_account);
|
||||
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
|
||||
account[_account] = oldBalance - _delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Creates new tokens out of thin air, and allocates them to the given address
|
||||
// Triggers tax
|
||||
function mintTo(address _beneficiary, uint256 _amount) external returns (bool) {
|
||||
uint256 baseAmount;
|
||||
|
||||
require(minter[msg.sender], 'ERR_ACCESS');
|
||||
|
||||
changePeriod();
|
||||
baseAmount = toBaseAmount(_amount);
|
||||
totalSupply += _amount;
|
||||
increaseBaseBalance(_beneficiary, baseAmount);
|
||||
emit Mint(msg.sender, _beneficiary, _amount);
|
||||
saveRedistributionSupply();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
|
||||
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
|
||||
redistribution |= bytes32(_period & maskRedistributionPeriod);
|
||||
return redistribution;
|
||||
}
|
||||
|
||||
// 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) & 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;
|
||||
}
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
|
||||
// Get the demurrage period of the current block number
|
||||
function actualPeriod() public view returns (uint128) {
|
||||
return uint128((block.timestamp - periodStart) / periodDuration + 1);
|
||||
}
|
||||
|
||||
// Add an entered demurrage period to the redistribution array
|
||||
function checkPeriod() private view returns (bytes32) {
|
||||
bytes32 lastRedistribution;
|
||||
uint256 currentPeriod;
|
||||
|
||||
lastRedistribution = redistributions[lastPeriod];
|
||||
currentPeriod = this.actualPeriod();
|
||||
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
|
||||
return bytes32(0x00);
|
||||
}
|
||||
return lastRedistribution;
|
||||
}
|
||||
|
||||
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
|
||||
uint256 difference;
|
||||
|
||||
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000));
|
||||
return difference / resolutionFactor;
|
||||
}
|
||||
|
||||
function getDistributionFromRedistribution(bytes32 _redistribution) public returns (uint256) {
|
||||
uint256 redistributionSupply;
|
||||
uint256 redistributionDemurrage;
|
||||
|
||||
redistributionSupply = toRedistributionSupply(_redistribution);
|
||||
redistributionDemurrage = toRedistributionDemurrageModifier(_redistribution);
|
||||
return getDistribution(redistributionSupply, redistributionDemurrage);
|
||||
}
|
||||
|
||||
// Returns the amount sent to the sink address
|
||||
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
|
||||
uint256 unit;
|
||||
uint256 baseUnit;
|
||||
|
||||
unit = getDistributionFromRedistribution(_redistribution);
|
||||
baseUnit = toBaseAmount(unit) - totalSink;
|
||||
increaseBaseBalance(sinkAddress, baseUnit);
|
||||
lastPeriod += 1;
|
||||
totalSink += baseUnit;
|
||||
return unit;
|
||||
}
|
||||
|
||||
// Calculate the time delta in whole minutes passed between given timestamp and current timestamp
|
||||
function getMinutesDelta(uint256 _lastTimestamp) public view returns (uint256) {
|
||||
return (block.timestamp - _lastTimestamp) / 60;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
uint256 periodCount;
|
||||
uint256 lastDemurrageAmount;
|
||||
|
||||
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);
|
||||
emit Decayed(demurrageTimestamp, periodCount, lastDemurrageAmount, demurrageAmount);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return timestamp of start of period threshold
|
||||
function getPeriodTimeDelta(uint256 _periodCount) public view returns (uint256) {
|
||||
return periodStart + (_periodCount * periodDuration);
|
||||
}
|
||||
|
||||
// Amount of demurrage cycles inbetween the current timestamp and the given target time
|
||||
function demurrageCycles(uint256 _target) public view returns (uint256) {
|
||||
return (block.timestamp - _target) / 60;
|
||||
}
|
||||
|
||||
// Recalculate the demurrage modifier for the new period
|
||||
function changePeriod() public returns (bool) {
|
||||
bytes32 currentRedistribution;
|
||||
bytes32 nextRedistribution;
|
||||
uint256 currentPeriod;
|
||||
uint256 currentDemurrageAmount;
|
||||
uint256 nextRedistributionDemurrage;
|
||||
uint256 demurrageCounts;
|
||||
uint256 periodTimestamp;
|
||||
uint256 nextPeriod;
|
||||
|
||||
applyDemurrage();
|
||||
currentRedistribution = checkPeriod();
|
||||
if (currentRedistribution == bytes32(0x00)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
redistributions.push(nextRedistribution);
|
||||
|
||||
applyDefaultRedistribution(nextRedistribution);
|
||||
emit Period(nextPeriod);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Calculate a value reduced by demurrage by the given period
|
||||
function decayBy(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;
|
||||
}
|
||||
|
||||
// Inflates the given amount according to the current demurrage modifier
|
||||
function toBaseAmount(uint256 _value) public view returns (uint256) {
|
||||
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function approve(address _spender, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
|
||||
changePeriod();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
allowance[msg.sender][_spender] += baseValue;
|
||||
emit Approval(msg.sender, _spender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function transfer(address _to, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
bool result;
|
||||
|
||||
changePeriod();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
result = transferBase(msg.sender, _to, baseValue);
|
||||
emit Transfer(msg.sender, _to, _value);
|
||||
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();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
require(allowance[_from][msg.sender] >= baseValue);
|
||||
|
||||
result = transferBase(_from, _to, baseValue);
|
||||
emit Transfer(_from, _to, _value);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ERC20 transfer backend for transfer, transferFrom
|
||||
function transferBase(address _from, address _to, uint256 _value) private returns (bool) {
|
||||
uint256 period;
|
||||
|
||||
decreaseBaseBalance(_from, _value);
|
||||
increaseBaseBalance(_to, _value);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements EIP173
|
||||
function transferOwnership(address _newOwner) public returns (bool) {
|
||||
require(msg.sender == owner);
|
||||
newOwner = _newOwner;
|
||||
}
|
||||
|
||||
// Implements OwnedAccepter
|
||||
function acceptOwnership() public returns (bool) {
|
||||
address oldOwner;
|
||||
|
||||
require(msg.sender == newOwner);
|
||||
oldOwner = owner;
|
||||
owner = newOwner;
|
||||
newOwner = address(0);
|
||||
emit OwnershipTransferred(oldOwner, owner);
|
||||
}
|
||||
|
||||
// Implements EIP165
|
||||
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
||||
if (_sum == 0xc6bb4b70) { // ERC20
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x449a52f8) { // Minter
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x01ffc9a7) { // EIP165
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x9493f8b2) { // EIP173
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x37a47be4) { // OwnedAccepter
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,38 +1,16 @@
|
||||
SOLC = /usr/bin/solc
|
||||
|
||||
all: multi single
|
||||
|
||||
multi_nocap:
|
||||
$(SOLC) DemurrageTokenMultiNocap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiNocap.json
|
||||
$(SOLC) DemurrageTokenMultiNocap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiNocap.bin
|
||||
truncate -s -1 DemurrageTokenMultiNocap.bin
|
||||
|
||||
multi_cap:
|
||||
$(SOLC) DemurrageTokenMultiCap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiCap.json
|
||||
$(SOLC) DemurrageTokenMultiCap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiCap.bin
|
||||
truncate -s -1 DemurrageTokenMultiCap.bin
|
||||
|
||||
multi: multi_nocap multi_cap
|
||||
|
||||
single_nocap:
|
||||
$(SOLC) DemurrageTokenSingleNocap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleNocap.json
|
||||
$(SOLC) DemurrageTokenSingleNocap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleNocap.bin
|
||||
truncate -s -1 DemurrageTokenSingleNocap.bin
|
||||
|
||||
single_cap:
|
||||
$(SOLC) DemurrageTokenSingleCap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleCap.json
|
||||
$(SOLC) DemurrageTokenSingleCap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleCap.bin
|
||||
truncate -s -1 DemurrageTokenSingleCap.bin
|
||||
|
||||
single: single_nocap single_cap
|
||||
all:
|
||||
$(SOLC) RedistributedDemurrageToken.sol --abi --evm-version byzantium | awk 'NR>3' > RedistributedDemurrageToken.json
|
||||
$(SOLC) RedistributedDemurrageToken.sol --bin --evm-version byzantium | awk 'NR>3' > RedistributedDemurrageToken.bin
|
||||
truncate -s -1 RedistributedDemurrageToken.bin
|
||||
|
||||
test: all
|
||||
python ../python/tests/test_basic.py
|
||||
python ../python/tests/test_period.py
|
||||
python ../python/tests/test_redistribution.py
|
||||
python ../python/tests/test_pure.py
|
||||
|
||||
install: all
|
||||
cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
|
||||
cp -v RedistributedDemurrageToken.{json,bin} ../python/sarafu_token/data/
|
||||
|
||||
.PHONY: test install
|
||||
|
@ -2,7 +2,7 @@ pragma solidity > 0.6.11;
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
contract DemurrageTokenMultiNocap {
|
||||
contract RedistributedDemurrageToken {
|
||||
|
||||
// Redistribution bit field, with associated shifts and masks
|
||||
// (Uses sub-byte boundaries)
|
||||
@ -56,9 +56,6 @@ contract DemurrageTokenMultiNocap {
|
||||
// (this constant x 1000000 is contained within 128 bits)
|
||||
uint256 constant ppmDivider = 100000000000000000000000000000000;
|
||||
|
||||
// demurrage decimal width; 38 places
|
||||
uint256 public immutable resolutionFactor = ppmDivider * 1000000;
|
||||
|
||||
// Timestamp of start of periods (time which contract constructor was called)
|
||||
uint256 public immutable periodStart;
|
||||
|
Reference in New Issue
Block a user