Cleanup, docs, benchmarks

This commit is contained in:
Louis Holbrook
2021-03-01 09:53:39 +00:00
parent 9de5e52c2f
commit 8e777aa720
14 changed files with 584 additions and 144 deletions

1
python/MANIFEST.in Normal file
View File

@@ -0,0 +1 @@
include sarafu_token/data/*

2
python/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
chainlib~=0.0.1a7
crypto-dev-signer~=0.4.13rc2

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,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()

41
python/setup.cfg Normal file
View File

@@ -0,0 +1,41 @@
[metadata]
name = sarafu-token
version = 0.0.1a2
description = ERC20 token with redistributed continual demurrage
author = Louis Holbrook
author_email = dev@holbrook.no
url = https://gitlab.com/grassrootseconomics/sarafu-token
keywords =
ethereum
classifiers =
Programming Language :: Python :: 3
Operating System :: OS Independent
Development Status :: 3 - Alpha
Environment :: No Input/Output (Daemon)
Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Topic :: Internet
#Topic :: Blockchain :: EVM
license = GPL3
licence_files =
LICENSE
[options]
include_package_data = True
python_requires = >= 3.6
packages =
sarafu_token
sarafu_token.runnable.legacy
install_requires =
chainlib~=0.0.1a7
crypto-dev-signer~=0.4.13rc2
web3==5.12.2
[options.package_data]
* =
data/RedistributedDemurrageToken.bin
data/RedistributedDemurrageToken.json
[options.entry_points]
console_scripts =
sarafu-token-deploy = sarafu_faucet.runnable.legacy.deploy:main

10
python/setup.py Normal file
View File

@@ -0,0 +1,10 @@
from setuptools import setup
setup(
package_data={
'': [
'data/MintableFactor.bin',
],
},
include_package_data=True,
)

View File

@@ -47,6 +47,14 @@ class Test(unittest.TestCase):
pass
def test_construct(self):
period = 10
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)
print('construct: {}'.format(r['gasUsed']))
def test_gas_changeperiod(self):
period = 43200
for i in range(5):
@@ -64,7 +72,155 @@ class Test(unittest.TestCase):
tx_hash = contract.functions.changePeriod().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
print('{} ({}): {}'.format(i, 60 * (10 ** i), r['gasUsed']))
print('changePeriod {} ({}): {}'.format(i, 60 * (10 ** i), r['gasUsed']))
def test_mint(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
tx_hash = contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact({'from': self.w3.eth.accounts[0]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
print ('mintTo: {}'.format(r['gasUsed']))
def test_transfer(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact({'from': self.w3.eth.accounts[0]})
tx_hash = contract.functions.transfer(self.w3.eth.accounts[2], 1000000).transact({'from': self.w3.eth.accounts[1]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
print ('transfer: {}'.format(r['gasUsed']))
def test_approve(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact({'from': self.w3.eth.accounts[0]})
tx_hash = contract.functions.approve(self.w3.eth.accounts[2], 1000000).transact({'from': self.w3.eth.accounts[1]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
print ('approve: {}'.format(r['gasUsed']))
def test_transferfrom(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact({'from': self.w3.eth.accounts[0]})
contract.functions.approve(self.w3.eth.accounts[2], 1000000).transact({'from': self.w3.eth.accounts[1]})
tx_hash = contract.functions.transferFrom(self.w3.eth.accounts[1], self.w3.eth.accounts[3], 1000000).transact({'from': self.w3.eth.accounts[2]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
print ('transferFrom: {}'.format(r['gasUsed']))
def test_redistribute_default(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
for i in range(100):
addr = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex())
contract.functions.mintTo(addr, 1000000 * (i+1)).transact({'from': self.w3.eth.accounts[0]})
self.eth_tester.time_travel(start_time + period * 60 + 1)
redistribution = contract.functions.redistributions(0).call()
tx_hash = contract.functions.changePeriod().transact({'from': self.w3.eth.accounts[2]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
print ('chainPeriod -> defaultRedistribution: {}'.format(r['gasUsed']))
def test_redistribution_account(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact({'from': self.w3.eth.accounts[0]})
contract.functions.transfer(self.w3.eth.accounts[2], 1000000).transact({'from': self.w3.eth.accounts[1]})
for i in range(100):
addr = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex())
contract.functions.mintTo(addr, 1000000 * (i+1)).transact({'from': self.w3.eth.accounts[0]})
self.eth_tester.time_travel(start_time + period * 60 + 1)
redistribution = contract.functions.redistributions(0).call()
tx_hash = contract.functions.applyRedistributionOnAccount(self.w3.eth.accounts[1]).transact({'from': self.w3.eth.accounts[2]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.logs[0].topics[0].hex(), '0x9a2a887706623ad3ff7fc85652deeceabe9fe1e00466c597972079ee91ea40d3')
print ('redistribute account: {}'.format(r['gasUsed']))
def test_redistribution_account_transfer(self):
period = 10
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)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
contract.functions.mintTo(self.w3.eth.accounts[1], 2000000).transact({'from': self.w3.eth.accounts[0]})
contract.functions.transfer(self.w3.eth.accounts[2], 1000000).transact({'from': self.w3.eth.accounts[1]})
for i in range(10):
addr = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex())
contract.functions.mintTo(addr, 1000000 * (i+1)).transact({'from': self.w3.eth.accounts[0]})
self.eth_tester.time_travel(start_time + period * 60 + 1)
redistribution = contract.functions.redistributions(0).call()
contract.functions.changePeriod().transact({'from': self.w3.eth.accounts[0]})
tx_hash = contract.functions.transfer(self.w3.eth.accounts[3], 100000).transact({'from': self.w3.eth.accounts[1]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.logs[0].topics[0].hex(), '0x9a2a887706623ad3ff7fc85652deeceabe9fe1e00466c597972079ee91ea40d3')
print ('redistribute account: {}'.format(r['gasUsed']))
if __name__ == '__main__':
unittest.main()

View File

@@ -71,25 +71,30 @@ class Test(unittest.TestCase):
self.assertEqual(self.contract.functions.actualPeriod().call(), 2)
def test_apply_demurrage(self):
modifier = 10 * (10 ** 37)
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_modifier &= (1 << 128) - 1
self.assertEqual(modifier, demurrage_modifier)
#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)
self.eth_tester.time_travel(self.start_time + 59)
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_modifier &= (1 << 128) - 1
self.assertEqual(modifier, demurrage_modifier)
#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.eth_tester.time_travel(self.start_time + 61)
tx_hash = self.contract.functions.applyDemurrage().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_modifier &= (1 << 128) - 1
self.assertEqual(int(98 * (10 ** 36)), demurrage_modifier)
#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):
@@ -111,7 +116,34 @@ class Test(unittest.TestCase):
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
self.assertEqual(balance, int(2000 * 0.98))
def test_minter_control(self):
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 = 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)
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.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[2]})
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]})
def test_base_amount(self):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
@@ -120,9 +152,9 @@ class Test(unittest.TestCase):
self.eth_tester.time_travel(self.start_time + 61)
self.contract.functions.applyDemurrage().transact()
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_amount = self.contract.functions.toDemurrageAmount(demurrage_modifier).call()
logg.debug('d {} {}'.format(demurrage_modifier.to_bytes(32, 'big').hex(), demurrage_amount))
#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)

View File

@@ -60,20 +60,21 @@ class Test(unittest.TestCase):
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.toTaxPeriodAmount(1000000, 0).call()
a = self.contract.functions.toDemurrageAmount(1000000, 0).call()
self.assertEqual(a, 1000000)
a = self.contract.functions.toTaxPeriodAmount(1000000, 1).call()
a = self.contract.functions.toDemurrageAmount(1000000, 1).call()
self.assertEqual(a, 980000)
a = self.contract.functions.toTaxPeriodAmount(1000000, 2).call()
a = self.contract.functions.toDemurrageAmount(1000000, 2).call()
self.assertEqual(a, 960400)
a = self.contract.functions.toTaxPeriodAmount(980000, 1).call()
a = self.contract.functions.toDemurrageAmount(980000, 1).call()
self.assertEqual(a, 960400)

View File

@@ -71,6 +71,9 @@ class Test(unittest.TestCase):
# 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()