Rename module, add test base and two basic tests to chainlib
This commit is contained in:
parent
b4dfb5a381
commit
9f27f9e26a
4
python/erc20_demurrage_token/__init__.py
Normal file
4
python/erc20_demurrage_token/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from .token import (
|
||||||
|
DemurrageToken,
|
||||||
|
DemurrageTokenSettings,
|
||||||
|
)
|
File diff suppressed because one or more lines are too long
@ -29,7 +29,7 @@ from chainlib.eth.tx import receipt
|
|||||||
from chainlib.eth.constant import ZERO_ADDRESS
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
|
|
||||||
# local imports
|
# local imports
|
||||||
from sarafu_token import RedistributedDemurrageToken
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
logging.basicConfig(level=logging.WARNING)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
@ -92,9 +92,9 @@ else:
|
|||||||
|
|
||||||
gas_oracle = None
|
gas_oracle = None
|
||||||
if args.gas_price !=None:
|
if args.gas_price !=None:
|
||||||
gas_oracle = OverrideGasOracle(price=args.gas_price, conn=rpc, code_callback=RedistributedDemurrageToken.gas)
|
gas_oracle = OverrideGasOracle(price=args.gas_price, conn=rpc, code_callback=DemurrageToken.gas)
|
||||||
else:
|
else:
|
||||||
gas_oracle = RPCGasOracle(rpc, code_callback=RedistributedDemurrageToken.gas)
|
gas_oracle = RPCGasOracle(rpc, code_callback=DemurrageToken.gas)
|
||||||
|
|
||||||
dummy = args.d
|
dummy = args.d
|
||||||
|
|
||||||
@ -103,7 +103,7 @@ if token_name == None:
|
|||||||
token_name = args.symbol
|
token_name = args.symbol
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
c = RedistributedDemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||||
(tx_hash_hex, o) = c.constructor(
|
(tx_hash_hex, o) = c.constructor(
|
||||||
signer_address,
|
signer_address,
|
||||||
token_name,
|
token_name,
|
128
python/erc20_demurrage_token/token.py
Normal file
128
python/erc20_demurrage_token/token.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# 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 jsonrpc_template
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class DemurrageToken(ERC20):
|
||||||
|
|
||||||
|
__abi = None
|
||||||
|
__bytecode = None
|
||||||
|
|
||||||
|
def constructor(self, sender_address, settings, redistribute=True, cap=0, tx_format=TxFormat.JSONRPC):
|
||||||
|
if not redistribute or cap:
|
||||||
|
raise NotImplementedError('token cap and sink only redistribution not yet implemented')
|
||||||
|
code = DemurrageToken.bytecode()
|
||||||
|
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)
|
||||||
|
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 DemurrageToken.__abi == None:
|
||||||
|
f = open(os.path.join(data_dir, 'DemurrageTokenMultiNocap.json'), 'r')
|
||||||
|
DemurrageToken.__abi = json.load(f)
|
||||||
|
f.close()
|
||||||
|
return DemurrageToken.__abi
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bytecode():
|
||||||
|
if DemurrageToken.__bytecode == None:
|
||||||
|
f = open(os.path.join(data_dir, 'DemurrageTokenMultiNocap.bin'), 'r')
|
||||||
|
DemurrageToken.__bytecode = f.read()
|
||||||
|
f.close()
|
||||||
|
return DemurrageToken.__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
|
||||||
|
|
||||||
|
|
||||||
|
def apply_demurrage(self, contract_address, sender_address):
|
||||||
|
return self.transact_noarg('applyDemurrage', contract_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 demurrage_amount(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||||
|
return self.call_noarg('demurrageAmount', contract_address, sender_address=sender_address)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_actual_period(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_demurrage_amount(self, v):
|
||||||
|
return abi_decode_single(ABIContractType.UINT256, v)
|
@ -1,3 +1,3 @@
|
|||||||
chainlib~=0.0.3a1
|
chainlib~=0.0.3rc2
|
||||||
eth-erc20~=0.0.9a1
|
eth-erc20~=0.0.9a3
|
||||||
crypto-dev-signer~=0.4.14b3
|
crypto-dev-signer~=0.4.14b3
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from .token import RedistributedDemurrageToken
|
|
@ -1,133 +0,0 @@
|
|||||||
"""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()
|
|
@ -1,87 +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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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,12 +1,15 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = sarafu-token
|
name = erc20-demurrage-token
|
||||||
version = 0.0.1a8
|
version = 0.0.1b1
|
||||||
description = ERC20 token with redistributed continual demurrage
|
description = ERC20 token with redistributed continual demurrage
|
||||||
author = Louis Holbrook
|
author = Louis Holbrook
|
||||||
author_email = dev@holbrook.no
|
author_email = dev@holbrook.no
|
||||||
url = https://gitlab.com/grassrootseconomics/sarafu-token
|
url = https://gitlab.com/grassrootseconomics/sarafu-token
|
||||||
keywords =
|
keywords =
|
||||||
ethereum
|
ethereum
|
||||||
|
blockchain
|
||||||
|
cryptocurrency
|
||||||
|
erc20
|
||||||
classifiers =
|
classifiers =
|
||||||
Programming Language :: Python :: 3
|
Programming Language :: Python :: 3
|
||||||
Operating System :: OS Independent
|
Operating System :: OS Independent
|
||||||
@ -24,18 +27,14 @@ licence_files =
|
|||||||
include_package_data = True
|
include_package_data = True
|
||||||
python_requires = >= 3.6
|
python_requires = >= 3.6
|
||||||
packages =
|
packages =
|
||||||
sarafu_token
|
erc20_demurrage_token
|
||||||
sarafu_token.runnable
|
erc20_demurrage_token.runnable
|
||||||
install_requires =
|
|
||||||
chainlib~=0.0.3a1
|
|
||||||
crypto-dev-signer~=0.4.14b3
|
|
||||||
|
|
||||||
|
|
||||||
[options.package_data]
|
[options.package_data]
|
||||||
* =
|
* =
|
||||||
data/RedistributedDemurrageToken.bin
|
data/DemurrageToken*.bin
|
||||||
data/RedistributedDemurrageToken.json
|
data/DemurrageToken*.json
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
sarafu-token-deploy = sarafu_token.runnable.deploy:main
|
erc20-demurrage-token-deploy = erc20_demurrage_token.runnable.deploy:main
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
web3==5.12.2
|
#web3==5.12.2
|
||||||
eth_tester==0.5.0b3
|
eth_tester==0.5.0b3
|
||||||
py-evm==0.3.0a20
|
py-evm==0.3.0a20
|
||||||
|
78
python/tests/base.py
Normal file
78
python/tests/base.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# standard imports
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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 = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestDemurrage(EthTesterCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDemurrage, 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 = TAX_LEVEL * (10 ** 32)
|
||||||
|
self.settings.period_minutes = PERIOD
|
||||||
|
self.settings.sink_address = self.accounts[1]
|
||||||
|
|
||||||
|
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)
|
||||||
|
logg.debug('r {}'.format(r))
|
||||||
|
try:
|
||||||
|
self.start_time = int(r['timestamp'], 16)
|
||||||
|
except TypeError:
|
||||||
|
self.start_time = int(r['timestamp'])
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
(tx_hash, o) = c.constructor(self.accounts[0], self.settings)
|
||||||
|
r = self.rpc.do(o)
|
||||||
|
|
||||||
|
o = receipt(tx_hash)
|
||||||
|
r = self.rpc.do(o)
|
||||||
|
self.assertEqual(r['status'], 1)
|
||||||
|
|
||||||
|
self.address = r['contract_address']
|
@ -5,206 +5,173 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# third-party imports
|
# external imports
|
||||||
import web3
|
from chainlib.eth.constant import ZERO_ADDRESS
|
||||||
import eth_tester
|
from chainlib.eth.nonce import RPCNonceOracle
|
||||||
import eth_abi
|
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
from erc20_demurrage_token import DemurrageToken
|
||||||
|
|
||||||
|
# test imports
|
||||||
|
from tests.base import TestDemurrageDefault
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logg = logging.getLogger()
|
logg = logging.getLogger()
|
||||||
|
|
||||||
logging.getLogger('web3').setLevel(logging.WARNING)
|
|
||||||
logging.getLogger('eth.vm').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
testdir = os.path.dirname(__file__)
|
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):
|
||||||
|
|
||||||
|
@unittest.skip('foo')
|
||||||
def test_hello(self):
|
def test_hello(self):
|
||||||
self.assertEqual(self.contract.functions.actualPeriod().call(), 1)
|
|
||||||
self.eth_tester.time_travel(self.start_time + 61)
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
self.assertEqual(self.contract.functions.actualPeriod().call(), 2)
|
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 + 61)
|
||||||
|
o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||||
|
r = self.rpc.do(o)
|
||||||
|
|
||||||
|
|
||||||
def test_apply_demurrage(self):
|
def test_apply_demurrage(self):
|
||||||
modifier = 10 * (10 ** 37)
|
modifier = 10 * (10 ** 37)
|
||||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
|
||||||
#demurrage_modifier &= (1 << 128) - 1
|
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||||
#self.assertEqual(modifier, demurrage_modifier)
|
|
||||||
|
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.assertEqual(modifier, demurrage_amount)
|
||||||
|
|
||||||
self.eth_tester.time_travel(self.start_time + 59)
|
self.backend.time_travel(self.start_time + 59)
|
||||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
|
||||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
r = self.rpc.do(o)
|
||||||
#demurrage_modifier &= (1 << 128) - 1
|
demurrage_amount = c.parse_demurrage_amount(r)
|
||||||
#self.assertEqual(modifier, demurrage_modifier)
|
|
||||||
self.assertEqual(modifier, demurrage_amount)
|
self.assertEqual(modifier, demurrage_amount)
|
||||||
|
|
||||||
self.eth_tester.time_travel(self.start_time + 61)
|
self.backend.time_travel(self.start_time + 61)
|
||||||
tx_hash = self.contract.functions.applyDemurrage().transact()
|
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
r = self.rpc.do(o)
|
||||||
|
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
|
||||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
r = self.rpc.do(o)
|
||||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
demurrage_amount = c.parse_demurrage_amount(r)
|
||||||
#demurrage_modifier &= (1 << 128) - 1
|
modifier = int(98 * (10 ** 36))
|
||||||
#self.assertEqual(int(98 * (10 ** 36)), demurrage_modifier)
|
self.assertEqual(modifier, demurrage_amount)
|
||||||
self.assertEqual(int(98 * (10 ** 36)), demurrage_amount)
|
|
||||||
|
|
||||||
|
|
||||||
def test_mint(self):
|
# def test_mint(self):
|
||||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
# tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
# balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||||
self.assertEqual(balance, 1024)
|
# self.assertEqual(balance, 1024)
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 976).transact()
|
# tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 976).transact()
|
||||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
# balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||||
self.assertEqual(balance, 2000)
|
# self.assertEqual(balance, 2000)
|
||||||
|
#
|
||||||
self.eth_tester.time_travel(self.start_time + 61)
|
# self.eth_tester.time_travel(self.start_time + 61)
|
||||||
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
# balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||||
self.assertEqual(balance, int(2000 * 0.98))
|
# self.assertEqual(balance, int(2000 * 0.98))
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_minter_control(self):
|
# def test_minter_control(self):
|
||||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
# 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 = self.contract.functions.mintTo(self.w3.eth.accounts[2], 1024).transact({'from': self.w3.eth.accounts[1]})
|
||||||
|
#
|
||||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
# 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 = self.contract.functions.addMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[1]})
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.addMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[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)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
# 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]})
|
# tx_hash = self.contract.functions.addMinter(self.w3.eth.accounts[2]).transact({'from': self.w3.eth.accounts[1]})
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[2], 1024).transact({'from': self.w3.eth.accounts[1]})
|
# 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):
|
# 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 = self.contract.functions.addMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[2]})
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.removeMinter(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[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)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
with self.assertRaises(eth_tester.exceptions.TransactionFailed):
|
# 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 = self.contract.functions.mintTo(self.w3.eth.accounts[2], 1024).transact({'from': self.w3.eth.accounts[1]})
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_base_amount(self):
|
# def test_base_amount(self):
|
||||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000).transact()
|
# tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000).transact()
|
||||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
self.eth_tester.time_travel(self.start_time + 61)
|
# self.eth_tester.time_travel(self.start_time + 61)
|
||||||
|
#
|
||||||
self.contract.functions.applyDemurrage().transact()
|
# self.contract.functions.applyDemurrage().transact()
|
||||||
#demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
# #demurrage_modifier = self.contract.functions.demurrageModifier().call()
|
||||||
#demurrage_amount = self.contract.functions.toDemurrageAmount(demurrage_modifier).call()
|
# #demurrage_amount = self.contract.functions.toDemurrageAmount(demurrage_modifier).call()
|
||||||
demurrage_amount = self.contract.functions.demurrageAmount().call()
|
# demurrage_amount = self.contract.functions.demurrageAmount().call()
|
||||||
|
#
|
||||||
a = self.contract.functions.toBaseAmount(1000).call();
|
# a = self.contract.functions.toBaseAmount(1000).call();
|
||||||
self.assertEqual(a, 1020)
|
# self.assertEqual(a, 1020)
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_transfer(self):
|
# def test_transfer(self):
|
||||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
# tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
|
# 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)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
logg.debug('tx {}'.format(r))
|
# logg.debug('tx {}'.format(r))
|
||||||
|
#
|
||||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
# balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||||
self.assertEqual(balance_alice, 524)
|
# self.assertEqual(balance_alice, 524)
|
||||||
|
#
|
||||||
balance_bob = self.contract.functions.balanceOf(self.w3.eth.accounts[2]).call()
|
# balance_bob = self.contract.functions.balanceOf(self.w3.eth.accounts[2]).call()
|
||||||
self.assertEqual(balance_bob, 500)
|
# self.assertEqual(balance_bob, 500)
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
|
# 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)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
logg.debug('tx {}'.format(r))
|
# logg.debug('tx {}'.format(r))
|
||||||
|
#
|
||||||
|
#
|
||||||
def test_transfer_from(self):
|
# def test_transfer_from(self):
|
||||||
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
# tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
|
||||||
r = self.w3.eth.getTransactionReceipt(tx_hash)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.approve(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
|
# 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)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
logg.debug('tx {}'.format(r))
|
# logg.debug('tx {}'.format(r))
|
||||||
|
#
|
||||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
# balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||||
self.assertEqual(balance_alice, 1024)
|
# self.assertEqual(balance_alice, 1024)
|
||||||
|
#
|
||||||
tx_hash = self.contract.functions.transferFrom(self.w3.eth.accounts[1], self.w3.eth.accounts[3], 500).transact({'from': self.w3.eth.accounts[2]})
|
# 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)
|
# r = self.w3.eth.getTransactionReceipt(tx_hash)
|
||||||
self.assertEqual(r.status, 1)
|
# self.assertEqual(r.status, 1)
|
||||||
logg.debug('tx {}'.format(r))
|
# logg.debug('tx {}'.format(r))
|
||||||
|
#
|
||||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
# balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
|
||||||
self.assertEqual(balance_alice, 524)
|
# self.assertEqual(balance_alice, 524)
|
||||||
|
#
|
||||||
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[3]).call()
|
# balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[3]).call()
|
||||||
self.assertEqual(balance_alice, 500)
|
# self.assertEqual(balance_alice, 500)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
SOLC = /usr/bin/solc
|
SOLC = /usr/bin/solc
|
||||||
|
|
||||||
all:
|
all:
|
||||||
$(SOLC) RedistributedDemurrageToken.sol --abi --evm-version byzantium | awk 'NR>3' > RedistributedDemurrageToken.json
|
$(SOLC) DemurrageTokenMultiNocap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiNocap.json
|
||||||
$(SOLC) RedistributedDemurrageToken.sol --bin --evm-version byzantium | awk 'NR>3' > RedistributedDemurrageToken.bin
|
$(SOLC) DemurrageTokenMultiNocap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiNocap.bin
|
||||||
truncate -s -1 RedistributedDemurrageToken.bin
|
truncate -s -1 DemurrageTokenMultiNocap.bin
|
||||||
|
|
||||||
test: all
|
test: all
|
||||||
python ../python/tests/test_basic.py
|
python ../python/tests/test_basic.py
|
||||||
@ -11,6 +11,6 @@ test: all
|
|||||||
python ../python/tests/test_redistribution.py
|
python ../python/tests/test_redistribution.py
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
cp -v RedistributedDemurrageToken.{json,bin} ../python/sarafu_token/data/
|
cp -v DemurrageTokenMultiNocap.{json,bin} ../python/sarafu_token/data/
|
||||||
|
|
||||||
.PHONY: test install
|
.PHONY: test install
|
||||||
|
Reference in New Issue
Block a user