mirror of
git://holbrook.no/erc20-demurrage-token
synced 2024-11-24 17:06:46 +01:00
Fix period test to recognize actual sink address
This commit is contained in:
parent
e6eef48808
commit
5c85a8abba
@ -1,3 +1,9 @@
|
|||||||
|
- 0.3.0
|
||||||
|
* Smart contracts use abdk math libraries, all exponential operations are static gas cost
|
||||||
|
* Add expiry features, after which balances are frozen and no more transfers or demurrage will occur
|
||||||
|
* Add sealable features for supply, sink address, expiry and minters (when sealed cannot be changed)
|
||||||
|
* Deployer script now takes demurrage amount as ppm instead of literal growth fraction
|
||||||
|
* Retire old multi and cap contracts
|
||||||
- 0.1.1
|
- 0.1.1
|
||||||
* Settable demurrage steps for apply demurrage cli tool
|
* Settable demurrage steps for apply demurrage cli tool
|
||||||
- 0.1.0
|
- 0.1.0
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1,47 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -x
|
set -a
|
||||||
set -e
|
set -e
|
||||||
|
set -x
|
||||||
export PYTHONPATH=.
|
default_pythonpath=$PYTHONPATH:.
|
||||||
|
export PYTHONPATH=${default_pythonpath:-.}
|
||||||
#modes=(MultiNocap MultiCap SingleCap SingleNocap)
|
>&2 echo using pythonpath $PYTHONPATH
|
||||||
#modes=(SingleCap SingleNocap) # other contracts need to be updted
|
for f in `ls tests/*.py`; do
|
||||||
modes=(SingleNocap) # other contracts need to be updted
|
python $f
|
||||||
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
|
done
|
||||||
|
|
||||||
#modes=(SingleCap) # other contracts need to be updted
|
|
||||||
modes=()
|
|
||||||
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)
|
|
||||||
modes=()
|
|
||||||
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
|
|
||||||
|
|
||||||
python tests/test_expiry.py
|
|
||||||
python tests/test_seal.py
|
|
||||||
python tests/test_cap.py
|
|
||||||
|
|
||||||
set +e
|
|
||||||
set +x
|
set +x
|
||||||
|
set +e
|
||||||
|
set +a
|
||||||
|
@ -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()
|
|
@ -172,7 +172,7 @@ class TestPeriod(TestDemurrageDefault):
|
|||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
self.assertEqual(r['status'], 1)
|
self.assertEqual(r['status'], 1)
|
||||||
|
|
||||||
o = c.balance_of(self.address, ZERO_ADDRESS, sender_address=self.accounts[0])
|
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
balance = c.parse_balance_of(r)
|
balance = c.parse_balance_of(r)
|
||||||
self.assertGreater(balance, 0)
|
self.assertGreater(balance, 0)
|
||||||
@ -222,7 +222,7 @@ class TestPeriod(TestDemurrageDefault):
|
|||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
self.assertEqual(r['status'], 1)
|
self.assertEqual(r['status'], 1)
|
||||||
|
|
||||||
o = c.balance_of(self.address, ZERO_ADDRESS, sender_address=self.accounts[0])
|
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||||
r = self.rpc.do(o)
|
r = self.rpc.do(o)
|
||||||
balance = c.parse_balance_of(r)
|
balance = c.parse_balance_of(r)
|
||||||
self.assertLess(balance, old_sink_balance)
|
self.assertLess(balance, old_sink_balance)
|
||||||
|
@ -1,328 +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_whole_is_parts(self):
|
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
|
||||||
|
|
||||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 100000000)
|
|
||||||
self.rpc.do(o)
|
|
||||||
o = receipt(tx_hash)
|
|
||||||
r = self.rpc.do(o)
|
|
||||||
self.assertEqual(r['status'], 1)
|
|
||||||
|
|
||||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[2], 100000000)
|
|
||||||
self.rpc.do(o)
|
|
||||||
o = receipt(tx_hash)
|
|
||||||
r = self.rpc.do(o)
|
|
||||||
self.assertEqual(r['status'], 1)
|
|
||||||
|
|
||||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
|
||||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
|
||||||
(tx_hash, o) = c.transfer(self.address, self.accounts[1], self.accounts[3], 50000000)
|
|
||||||
r = self.rpc.do(o)
|
|
||||||
o = receipt(tx_hash)
|
|
||||||
r = self.rpc.do(o)
|
|
||||||
self.assertEqual(r['status'], 1)
|
|
||||||
|
|
||||||
self.backend.time_travel(self.start_time + self.period_seconds + 1)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
(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)
|
|
||||||
|
|
||||||
(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)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
self.assertEqual(balance, 200000000)
|
|
||||||
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
#
|
|
||||||
|
|
||||||
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,614 +0,0 @@
|
|||||||
pragma solidity > 0.6.11;
|
|
||||||
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
contract DemurrageTokenMultiNocap {
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// 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) public {
|
|
||||||
// ACL setup
|
|
||||||
owner = msg.sender;
|
|
||||||
minter[owner] = true;
|
|
||||||
|
|
||||||
// ERC20 setup
|
|
||||||
name = _name;
|
|
||||||
symbol = _symbol;
|
|
||||||
decimals = _decimals;
|
|
||||||
|
|
||||||
// Demurrage setup
|
|
||||||
periodStart = block.timestamp;
|
|
||||||
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
|
|
||||||
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;
|
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
changePeriod();
|
|
||||||
baseAmount = _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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 and cache the demurrage value corresponding to the (period of the) time of the method call
|
|
||||||
function applyDemurrage() public returns (bool) {
|
|
||||||
uint128 epochPeriodCount;
|
|
||||||
uint128 periodCount;
|
|
||||||
uint256 lastDemurrageAmount;
|
|
||||||
uint256 newDemurrageAmount;
|
|
||||||
|
|
||||||
epochPeriodCount = actualPeriod();
|
|
||||||
periodCount = epochPeriodCount - demurragePeriod;
|
|
||||||
if (periodCount == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
lastDemurrageAmount = demurrageAmount;
|
|
||||||
demurrageAmount = uint128(decayBy(lastDemurrageAmount, periodCount));
|
|
||||||
demurragePeriod = epochPeriodCount;
|
|
||||||
emit Decayed(epochPeriodCount, 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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -189,12 +189,11 @@ contract DemurrageTokenSingleCap {
|
|||||||
function setMaxSupply(uint256 _cap) public {
|
function setMaxSupply(uint256 _cap) public {
|
||||||
require(!isSealed(CAP_STATE));
|
require(!isSealed(CAP_STATE));
|
||||||
require(msg.sender == owner);
|
require(msg.sender == owner);
|
||||||
require(_cap > supply);
|
require(_cap > totalSupply());
|
||||||
emit Cap(maxSupply, _cap);
|
emit Cap(maxSupply, _cap);
|
||||||
maxSupply = _cap;
|
maxSupply = _cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Change sink address for redistribution
|
// Change sink address for redistribution
|
||||||
function setSinkAddress(address _sinkAddress) public {
|
function setSinkAddress(address _sinkAddress) public {
|
||||||
require(!isSealed(SINK_STATE));
|
require(!isSealed(SINK_STATE));
|
||||||
|
@ -2,29 +2,12 @@ SOLC = /usr/bin/solc
|
|||||||
|
|
||||||
all: single_nocap
|
all: single_nocap
|
||||||
|
|
||||||
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:
|
single_nocap:
|
||||||
$(SOLC) DemurrageTokenSingleNocap.sol --abi --evm-version byzantium | awk 'NR==4' > DemurrageTokenSingleNocap.json
|
$(SOLC) DemurrageTokenSingleNocap.sol --abi --evm-version byzantium | awk 'NR==4' > DemurrageTokenSingleNocap.json
|
||||||
$(SOLC) DemurrageTokenSingleNocap.sol --bin --evm-version byzantium | awk 'NR==4' > DemurrageTokenSingleNocap.bin
|
$(SOLC) DemurrageTokenSingleNocap.sol --bin --evm-version byzantium | awk 'NR==4' > DemurrageTokenSingleNocap.bin
|
||||||
truncate -s -1 DemurrageTokenSingleNocap.bin
|
truncate -s -1 DemurrageTokenSingleNocap.bin
|
||||||
|
|
||||||
single_cap:
|
single: single_nocap
|
||||||
$(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
|
|
||||||
|
|
||||||
test: all
|
test: all
|
||||||
python ../python/tests/test_basic.py
|
python ../python/tests/test_basic.py
|
||||||
|
Loading…
Reference in New Issue
Block a user