mirror of
git://holbrook.no/erc20-demurrage-token
synced 2024-12-22 03:57:31 +01:00
Merge branch lash/readmemore
This commit is contained in:
commit
acd5eb3d7b
144
README.md
144
README.md
@ -1,22 +1,20 @@
|
||||
# RedistributedDemurrageToken
|
||||
|
||||
## Use Case
|
||||
* Network / Basic Income Token
|
||||
* 100 Sarafu is distributed to anyone in Kenya after user validation by the owner of a faucet which mints new Sarafu.
|
||||
* Validated users are those that validate their phone number in Kenya.
|
||||
* A Sarafu holding tax aka ([demurrage](https://en.wikipedia.org/wiki/Demurrage_(currency))) of 0.000050105908373373% is charged from users per minute - such that over 1 month to total tax would be 2%.
|
||||
* After 1 week the total amount tax is distributed evenly out to _active_ users.
|
||||
* any single transaction by a user within that week is considered _active_ (heartbeat)
|
||||
* This is meant to result in a disincentivization to hold (hodl) the Sarafu token and increase its usage as a medium of exchange rather than a store of value.
|
||||
* This token can be added to liquidity pools with other ERC20 tokens and or Community Inclusion Currencies (CICs) - and thereby act as a central network token and connect various tokens and CICs together.
|
||||
* Vouchers
|
||||
* A Publisher may publish a RedistributedDemurrageToken (Voucher) representing a credit obligation of an Issuer or Association of Issuers that can be redeemed as payment for the products of the Issuer. The Issuer is the entity legally obligated to redeem the voucher as payment.
|
||||
* Decay: The Publisher can specify an decay rate such as 2% as well as a redistribution period. After the redistribution period such as a month. Assuming an account holder has not had any transfers they will have a new balance of their original balance*2%. Note that the numeric decay will happen continuously by the minute.
|
||||
* Redistribution: The missing (demurraged) balances will be added to the balance of the SINK address. So once a redistribution period (e.g. once a month) the total supply of all holders including the SINK will return to the minted supply.
|
||||
* This is meant to result as a disincentivization to hold (hodl) the Voucher without causing price inflation, as the total supply is stable.
|
||||
* Example
|
||||
- With a demurrage of 2% (net per month) and a reward period of 1 month - If there are 10 users all with balances of 1000 Sarafu and only 2 of them trade that month (assume they trade back and forth with no net balance change).
|
||||
- Then the resulting balances after one tax period of those two trading would be 1080 Sarafu while the remaining non-active users would be 980 Sarafu. If this behaviour continued in the next tax period, with the same two users only trading (with no net balance changes), they would have 1158.39999968 Sarafu and those users that are not trading would have their balances further reduced to 960.40 Sarafu. If this continued on ~forever those two active trading users would have the entire token supply and the non-trading users would eventually reach a zero balance.
|
||||
- this example calculation for 3 tax periods can be found here: https://gitlab.com/grassrootseconomics/cic-docs/-/blob/master/demurrage-redist-sarafu.ods
|
||||
- With a demurrage of 2% (and redistribution period of 1 month) - If there are 10 users all with balances of 100 Vouchers (and only 2 of them trade that month (assume they trade back and forth with no net balance change)).
|
||||
- Then the resulting balances after one redistribution period of ALL users (regardless of their trading) would be 98 Vouchers and 20 Voucher would be the balance of the SINK address. Assuming the SINK address is redistributed (as a Community Fund) back to users, it’s balance would again reach 20 the next redistribution period.
|
||||
- Note that after the redistribution the total of all balances will equal the total minted amount.
|
||||
- Note that all accounts holding such Vouchers are effected by demurrage.
|
||||
|
||||
## Nomenclature
|
||||
|
||||
* `Demurrage` aka Decay amount: A percentage of token supply that will be charged once per minute and evenly redistributed to _active_ users every Demurrage Period (minutes)
|
||||
* `Demurrage` aka Decay amount: A percentage of token supply that will gradually be removed over a redstribution period and then redistributed to the SINK account.
|
||||
* Base balance: The inflated balance of each user is stored for bookkeeping.
|
||||
* Sink Token Address: Rounding errors and if no one trades the tax goes to this address
|
||||
* Demurrage Period (minutes)- aka `period`: The number of minutes over which a user must be _active_ to receive tax-redistibution.
|
||||
@ -30,63 +28,103 @@
|
||||
|
||||
## Mint
|
||||
|
||||
* Owner can add minters and remove
|
||||
- A faucet contract would be a minter and choose the amount of tokens to mint and distribute to new _validated_ users.
|
||||
- The interface says the amount and is at the caller's discretion per contract call. _validation_ is outside of this contract.
|
||||
* A minter can remove itself
|
||||
* Minters can mint any amount
|
||||
* Minters are called writers. Contract owner can add and remove writers.
|
||||
* A writer can remove itself
|
||||
* The interface says the amount and is at the caller's discretion per contract call. _validation_ is outside of this contract.
|
||||
* Writers can mint any amount. If supply cap is set, minting will be limited to this cap.
|
||||
|
||||
|
||||
## Demurrage
|
||||
* Holding Tax (`demurrage`) is applied when a **mint** or **transfer**; (it can also be triggered explicitly)
|
||||
- Note that the token supply _stays the same_ but a virtual _balance output_ is created.
|
||||
- Updates `demurrageModifier` which represents the accumulated tax value and is an exponential decay step (of size `demurrage`) for each minute that has passed.
|
||||
- `demurrageModifier = (1-demurrage)^(minute_passed)`
|
||||
- e.g. a `demurrage` of 2% after the 1st minute would be give a `demurrageModifier = (1-0.02)^1 = 0.98`.
|
||||
- e.g. a `demurrage` after the 2nd minute would be give a `demurrageModifier = (1-0.02)^2 = 0.9604`.
|
||||
* All client-facing values (_balance output_ , _transfer inputs_) are adjusted with `demurrageModifier`.
|
||||
- e.g. `_balance output_ = user_balance - user_balance * demurrageModifier`
|
||||
## Input parameters
|
||||
|
||||
The redistrbution period is passed to the contract in minutes. E.g. a redistribution period of one month would be approximately 43200 minutes.
|
||||
|
||||
The demurrage level specified as the percentage of continuous growth per minute:
|
||||
|
||||
`(1 - percentage) ^ (1 / period)`
|
||||
|
||||
E.g. A demurrage of 2% monthly would be defined as:
|
||||
|
||||
`(1 - 0.02) ^ (1 / 43200) ~ 0.99999953234484737109`
|
||||
|
||||
The number must be provided to the contract as a 64x64 bit fixed-point number (where the integer part is 0).
|
||||
|
||||
A script is included in the python package to publish the contract which takes the input as a percentage as parts-per-million and converts the correct input argument for the contract. The calculation can be found in the function `process_config_local` in `python/erc20_demurrage_token/runnable/publish.py`. It uses the python module [dexif](https://pypi.org/project/dexif/) to perform the fixed-point conversion.
|
||||
|
||||
|
||||
## Demurrage calculation
|
||||
|
||||
The demurrage calculation inside the contract is done by the following formula, where `demurrageLevel` is the demurrage level input parameter of the contract:
|
||||
|
||||
`newDemurrageModifier = currentDemurrageModifier * (e ^ (ln(demurrageLevel) * minutes))`
|
||||
|
||||
Holding Tax (`demurrage`) is applied when a **mint** or **transfer**; (it can also be triggered explicitly)
|
||||
- Note that the token supply _stays the same_ but a virtual _balance output_ is created.
|
||||
- Updates `demurrageModifier` which represents the accumulated tax value and is an exponential decay step (of size `demurrage`) for each minute that has passed.
|
||||
|
||||
|
||||
All client-facing values (_balance output_ , _transfer inputs_) are adjusted with `demurrageModifier`.
|
||||
|
||||
e.g. `_balance output_ = user_balance - user_balance * demurrageModifier`
|
||||
|
||||
|
||||
## Redistribution
|
||||
|
||||
* One redistribution entry is added to storage for each `period`;
|
||||
- When `mint` is triggered, the new totalsupply is stored to the entry
|
||||
- When `transfer` is triggered, and the account did not yet participate in the `period`, the entry's participant count is incremented.
|
||||
* Account must have "participated" in a period to be redistribution beneficiary.
|
||||
* Redistribution is applied when an account triggers a **transfer** for the first time in a new `period`;
|
||||
- Check if user has participated in `period`. (_active_ user heartbeat)
|
||||
- Each _active_ user balance in the `period` is increased by `(total supply at end of period * demurrageModifier ) / number_of_active_participants` via minting
|
||||
- Participation field is zeroed out for that user.
|
||||
* Fractions must be rounded down
|
||||
- Remainder is "dust" and should be sent to a dedicated Sink Token Address.
|
||||
- If no one is _active_ all taxes go to the Sink Token Address.
|
||||
* When `mint` is triggered, the new totalsupply is stored to the entry
|
||||
* When `transfer` is triggered, and the account did not yet participate in the `period`, the entry's participant count is incremented.
|
||||
* Redistributed tokens are added to the balance of the _sink address_ given when the contract is published.
|
||||
* _sink address_ may be changed.
|
||||
|
||||
|
||||
## Data structures
|
||||
## Data representation
|
||||
|
||||
* One word per `account`:
|
||||
- bits 000-071: value
|
||||
- bits 072-103: period
|
||||
- bits 104-255: (Unused)
|
||||
* One word per `redistributions` period:
|
||||
- bits 000-031: period
|
||||
- bits 032-103: supply
|
||||
- bits 104-139: participant count
|
||||
- bits 140-159: demurrage modifier
|
||||
- bits 160-254: (Unused)
|
||||
- bits 255: Set if individual redistribution amounts are fractions
|
||||
Token parameters are truncated when calculating demurrage and redistribution:
|
||||
|
||||
### Notes
|
||||
* Redistribution period: 32 bits
|
||||
* Token supply: 72 bits
|
||||
* Demurrage modifier: 64 bits
|
||||
|
||||
Accumulated demurrage modifier in `demurrageModifier` is 128 bit, but will be _truncated_ do 20 bits in `redistributions`. The 128 bit resolution is to used to reduce the impact of fractional drift of the long-term accumulation of the demurrage modifier. However, the demurrage snapshot values used in `redistributions` are parts-per-million and can be fully contained within a 20-bit value.
|
||||
|
||||
## Expiration
|
||||
|
||||
A token may set to expire at a certain point in time. After the expiry, no more transfers may be executed. From that point on, balances are frozen and demurrage is halted.
|
||||
|
||||
Expiration may be set in terms of redistribution periods.
|
||||
|
||||
Unless sealed (see below), expiration may be changed at any time to any future redistribution period. However, once expired, expiration may not be changed further.
|
||||
|
||||
|
||||
## Supply
|
||||
|
||||
Unless sealed (see below), Supply limit may be set and change at any time. Supply may never be directly set to less than the current supply. However, contract _writers_ may burn tokens in their possession using the `burn()` method, which will effectively reduce the supply.
|
||||
|
||||
|
||||
## Mutability
|
||||
|
||||
The following parameters may not be changed after contract is published:
|
||||
|
||||
* Demurrage level
|
||||
* Redistribution period
|
||||
|
||||
The contract provides a sealing feature which prohibits further changes to parameters that can initially be edited. These include:
|
||||
|
||||
* Adding and removing writers (addresses that may mint tokens)
|
||||
* Sink addres
|
||||
* Expiry period
|
||||
* Supply limit
|
||||
|
||||
|
||||
## Gas usage
|
||||
|
||||
The token contract uses the [ADBKMath](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol) library to calculate exponentials.
|
||||
|
||||
Gas usage is constant regardless of the amount of time passed between each execution of demurrage and redistribution period calculations.
|
||||
|
||||
|
||||
## QA
|
||||
|
||||
* Basic python tests in place
|
||||
* How to determine and generate sufficient test vectors, and how to adapt them to scripts.
|
||||
* Audit sources?
|
||||
* Tests are implemented using the `chaintool` python package suite.
|
||||
|
||||
|
||||
## Known issues
|
||||
|
||||
|
@ -1,3 +1,19 @@
|
||||
- 0.4.2
|
||||
* Correct burn interface implementation
|
||||
- 0.4.1
|
||||
* Eliminate solidity compiler warnings
|
||||
- 0.4.0
|
||||
* Rename taxLevel to decayLevel in contract
|
||||
* Add sweep contract method to fully empty one account into another
|
||||
- 0.3.6
|
||||
* Reinstate owner as minter by default
|
||||
- 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
|
||||
* Replace contract bitfields for redistributions with structs
|
||||
- 0.2.0
|
||||
* Add token burn function
|
||||
* Fix gas leak when calculating decay on period change
|
||||
|
@ -1,4 +1,8 @@
|
||||
from .token import (
|
||||
DemurrageToken,
|
||||
DemurrageTokenSettings,
|
||||
DemurrageRedistribution,
|
||||
)
|
||||
from .token import create
|
||||
from .token import bytecode
|
||||
from .token import args
|
||||
|
0
python/erc20_demurrage_token/data/.chainlib
Normal file
0
python/erc20_demurrage_token/data/.chainlib
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
36
python/erc20_demurrage_token/expiry.py
Normal file
36
python/erc20_demurrage_token/expiry.py
Normal file
@ -0,0 +1,36 @@
|
||||
# external imports
|
||||
from chainlib.eth.tx import (
|
||||
TxFactory,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
abi_decode_single,
|
||||
)
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
|
||||
|
||||
class ExpiryContract(TxFactory):
|
||||
|
||||
def set_expire_period(self, contract_address, sender_address, expire_timestamp, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('setExpirePeriod')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(expire_timestamp)
|
||||
data = enc.get()
|
||||
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||
tx = self.set_code(tx, data)
|
||||
tx = self.finalize(tx, tx_format)
|
||||
return tx
|
||||
|
||||
|
||||
def expires(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('expires', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_expires(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
@ -14,12 +14,21 @@ import logging
|
||||
|
||||
# external imports
|
||||
import confini
|
||||
from funga.eth.signer import EIP155Signer
|
||||
from funga.eth.keystore.dict import DictKeystore
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.nonce import (
|
||||
RPCNonceOracle,
|
||||
OverrideNonceOracle,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
RPCGasOracle,
|
||||
OverrideGasOracle,
|
||||
)
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
import chainlib.eth.cli
|
||||
from chainlib.eth.settings import process_settings
|
||||
from chainlib.settings import ChainSettings
|
||||
from chainlib.eth.cli.arg import (
|
||||
Arg,
|
||||
ArgFlag,
|
||||
@ -30,6 +39,11 @@ from chainlib.eth.cli.config import (
|
||||
process_config,
|
||||
)
|
||||
from chainlib.eth.cli.log import process_log
|
||||
from chainlib.eth.settings import process_settings
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.settings import ChainSettings
|
||||
|
||||
from dexif import to_fixed
|
||||
|
||||
# local imports
|
||||
import erc20_demurrage_token
|
||||
@ -40,32 +54,41 @@ from erc20_demurrage_token import (
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
data_dir = os.path.join(script_dir, '..', 'data')
|
||||
|
||||
config_dir = os.path.join(data_dir, 'config')
|
||||
|
||||
|
||||
def process_config_local(config, arg, args, flags):
|
||||
config.add(args.token_name, 'TOKEN_NAME', False)
|
||||
config.add(args.token_symbol, 'TOKEN_SYMBOL', False)
|
||||
config.add(args.token_decimals, 'TOKEN_DECIMALS', False)
|
||||
config.add(args.sink_address, 'TOKEN_SINK_ADDRESS', False)
|
||||
config.add(args.redistribution_period, 'TOKEN_REDISTRIBUTION_PERIOD', False)
|
||||
config.add(args.demurrage_level, 'TOKEN_DEMURRAGE_LEVEL', False)
|
||||
config.add(0, 'TOKEN_SUPPLY_LIMIT', False)
|
||||
config.add(args.token_name, 'TOKEN_NAME')
|
||||
config.add(args.token_symbol, 'TOKEN_SYMBOL')
|
||||
config.add(args.token_decimals, 'TOKEN_DECIMALS')
|
||||
sink_address = to_checksum_address(args.sink_address)
|
||||
config.add(sink_address, 'TOKEN_SINK_ADDRESS')
|
||||
config.add(args.redistribution_period, 'TOKEN_REDISTRIBUTION_PERIOD')
|
||||
|
||||
v = (1 - (args.demurrage_level / 1000000)) ** (1 / config.get('TOKEN_REDISTRIBUTION_PERIOD'))
|
||||
if v >= 1.0:
|
||||
raise ValueError('demurrage level must be less than 100%')
|
||||
demurrage_level = to_fixed(v)
|
||||
logg.info('v {} demurrage level {}'.format(v, demurrage_level))
|
||||
config.add(demurrage_level, 'TOKEN_DEMURRAGE_LEVEL')
|
||||
return config
|
||||
|
||||
|
||||
arg_flags = ArgFlag()
|
||||
arg = Arg(arg_flags)
|
||||
flags = arg_flags.STD_WRITE | arg_flags.EXEC | arg_flags.WALLET
|
||||
flags = arg_flags.STD_WRITE | arg_flags.WALLET
|
||||
|
||||
argparser = chainlib.eth.cli.ArgumentParser()
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser = process_args(argparser, arg, flags)
|
||||
argparser.add_argument('--name', dest='token_name', type=str, help='Token name')
|
||||
argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol')
|
||||
argparser.add_argument('--decimals', dest='token_decimals', type=int, help='Token decimals')
|
||||
argparser.add_argument('--sink-address', dest='sink_address', type=str, help='demurrage level,ppm per minute')
|
||||
#argparser.add_argument('--supply-limit', dest='supply_limit', type=int, help='token supply limit (0 = no limit)')
|
||||
argparser.add_argument('--redistribution-period', dest='redistribution_period', type=int, help='redistribution period, minutes (0 = deactivate)') # default 10080 = week
|
||||
#argparser.add_argument('--multi', action='store_true', help='automatic redistribution')
|
||||
argparser.add_argument('--demurrage-level', dest='demurrage_level', type=int, help='demurrage level, ppm per minute')
|
||||
argparser.add_argument('--redistribution-period', type=int, help='redistribution period, minutes (0 = deactivate)') # default 10080 = week
|
||||
argparser.add_argument('--demurrage-level', dest='demurrage_level', type=int, help='demurrage level, ppm per period')
|
||||
args = argparser.parse_args()
|
||||
|
||||
logg = process_log(args, logg)
|
||||
@ -80,16 +103,45 @@ settings = process_settings(settings, config)
|
||||
logg.debug('settings loaded:\n{}'.format(settings))
|
||||
|
||||
|
||||
#extra_args = {
|
||||
# 'redistribution_period': 'TOKEN_REDISTRIBUTION_PERIOD',
|
||||
# 'demurrage_level': 'TOKEN_DEMURRAGE_LEVEL',
|
||||
# 'supply_limit': 'TOKEN_SUPPLY_LIMIT',
|
||||
# 'token_name': 'TOKEN_NAME',
|
||||
# 'token_symbol': 'TOKEN_SYMBOL',
|
||||
# 'token_decimals': 'TOKEN_DECIMALS',
|
||||
# 'sink_address': 'TOKEN_SINK_ADDRESS',
|
||||
# 'multi': None,
|
||||
# }
|
||||
#config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_fee_limit=DemurrageToken.gas(), base_config_dir=config_dir)
|
||||
#
|
||||
#if not bool(config.get('TOKEN_NAME')):
|
||||
# logg.info('token name not set, using symbol {} as name'.format(config.get('TOKEN_SYMBOL')))
|
||||
# config.add(config.get('TOKEN_SYMBOL'), 'TOKEN_NAME', True)
|
||||
#
|
||||
#if config.get('TOKEN_SUPPLY_LIMIT') == None:
|
||||
# config.add(0, 'TOKEN_SUPPLY_LIMIT', True)
|
||||
#
|
||||
#if config.get('TOKEN_REDISTRIBUTION_PERIOD') == None:
|
||||
# config.add(10800, 'TOKEN_REDISTRIBUTION_PERIOD', True)
|
||||
#logg.debug('config loaded:\n{}'.format(config))
|
||||
#
|
||||
#wallet = chainlib.eth.cli.Wallet()
|
||||
#wallet.from_config(config)
|
||||
#
|
||||
#rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||
#conn = rpc.connect_by_config(config)
|
||||
#
|
||||
#chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
|
||||
def main():
|
||||
chain_spec = settings.get('CHAIN_SPEC')
|
||||
conn = settings.get('CONN')
|
||||
signer = settings.get('SIGNER')
|
||||
signer_address = settings.get('SENDER_ADDRESS')
|
||||
|
||||
gas_oracle = settings.get('FEE_ORACLE')
|
||||
nonce_oracle = settings.get('NONCE_ORACLE')
|
||||
|
||||
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
c = DemurrageToken(
|
||||
settings.get('CHAIN_SPEC'),
|
||||
signer=settings.get('SIGNER'),
|
||||
gas_oracle=settings.get('FEE_ORACLE'),
|
||||
nonce_oracle=settings.get('NONCE_ORACLE'),
|
||||
)
|
||||
token_settings = DemurrageTokenSettings()
|
||||
token_settings.name = config.get('TOKEN_NAME')
|
||||
token_settings.symbol = config.get('TOKEN_SYMBOL')
|
||||
@ -99,10 +151,8 @@ def main():
|
||||
token_settings.sink_address = config.get('TOKEN_SINK_ADDRESS')
|
||||
|
||||
(tx_hash_hex, o) = c.constructor(
|
||||
signer_address,
|
||||
settings.get('SENDER_ADDRESS'),
|
||||
token_settings,
|
||||
redistribute=config.true('_MULTI'),
|
||||
cap=int(config.get('TOKEN_SUPPLY_LIMIT')),
|
||||
)
|
||||
if settings.get('RPC_SEND'):
|
||||
conn.do(o)
|
1
python/erc20_demurrage_token/runnable/.#publish.py
Symbolic link
1
python/erc20_demurrage_token/runnable/.#publish.py
Symbolic link
@ -0,0 +1 @@
|
||||
wor@gecon.733148:1676287007
|
@ -1,152 +0,0 @@
|
||||
"""Deploy sarafu token
|
||||
|
||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
||||
|
||||
"""
|
||||
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
import datetime
|
||||
import math
|
||||
|
||||
# external imports
|
||||
import confini
|
||||
import chainlib.eth.cli
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
Block,
|
||||
)
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from hexathon import to_int as hex_to_int
|
||||
import chainlib.eth.cli
|
||||
from chainlib.eth.settings import process_settings
|
||||
from chainlib.settings import ChainSettings
|
||||
from chainlib.eth.cli.arg import (
|
||||
Arg,
|
||||
ArgFlag,
|
||||
process_args,
|
||||
)
|
||||
from chainlib.eth.cli.config import (
|
||||
Config,
|
||||
process_config,
|
||||
)
|
||||
from chainlib.eth.cli.log import process_log
|
||||
|
||||
# local imports
|
||||
import erc20_demurrage_token
|
||||
from erc20_demurrage_token import (
|
||||
DemurrageToken,
|
||||
DemurrageTokenSettings,
|
||||
)
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
def process_config_local(config, arg, args, flags):
|
||||
config.add(args.steps, '_STEPS', False)
|
||||
return config
|
||||
|
||||
|
||||
arg_flags = ArgFlag()
|
||||
arg = Arg(arg_flags)
|
||||
flags = arg_flags.STD_WRITE | arg_flags.EXEC | arg_flags.WALLET
|
||||
|
||||
argparser = chainlib.eth.cli.ArgumentParser()
|
||||
argparser = process_args(argparser, arg, flags)
|
||||
argparser.add_argument('--steps', type=int, default=0, help='Max demurrage steps to apply per round')
|
||||
args = argparser.parse_args()
|
||||
|
||||
logg = process_log(args, logg)
|
||||
|
||||
config = Config()
|
||||
config = process_config(config, arg, args, flags)
|
||||
config = process_config_local(config, arg, args, flags)
|
||||
logg.debug('config loaded:\n{}'.format(config))
|
||||
|
||||
settings = ChainSettings()
|
||||
settings = process_settings(settings, config)
|
||||
logg.debug('settings loaded:\n{}'.format(settings))
|
||||
|
||||
|
||||
def main():
|
||||
chain_spec = settings.get('CHAIN_SPEC')
|
||||
conn = settings.get('CONN')
|
||||
o = block_latest()
|
||||
r = conn.do(o)
|
||||
|
||||
block_start_number = None
|
||||
try:
|
||||
block_start_number = hex_to_int(r)
|
||||
except TypeError:
|
||||
block_start_number = int(r)
|
||||
|
||||
o = block_by_number(block_start_number)
|
||||
r = conn.do(o)
|
||||
|
||||
block_start = Block(r)
|
||||
block_start_timestamp = block_start.timestamp
|
||||
block_start_datetime = datetime.datetime.fromtimestamp(block_start_timestamp)
|
||||
|
||||
gas_oracle = settings.get('FEE_ORACLE')
|
||||
c = DemurrageToken(chain_spec, gas_oracle=gas_oracle)
|
||||
o = c.demurrage_timestamp(settings.get('EXEC'))
|
||||
r = conn.do(o)
|
||||
|
||||
demurrage_timestamp = None
|
||||
try:
|
||||
demurrage_timestamp = hex_to_int(r)
|
||||
except TypeError:
|
||||
demurrage_timestamp = int(r)
|
||||
demurrage_datetime = datetime.datetime.fromtimestamp(demurrage_timestamp)
|
||||
|
||||
total_seconds = block_start_timestamp - demurrage_timestamp
|
||||
total_steps = total_seconds / 60
|
||||
|
||||
if total_steps < 1.0:
|
||||
logg.error('only {} seconds since last demurrage application, skipping'.format(total_seconds))
|
||||
return
|
||||
|
||||
logg.debug('block start is at {} demurrage is at {} -> {} minutes'.format(
|
||||
block_start_datetime,
|
||||
demurrage_datetime,
|
||||
total_steps,
|
||||
))
|
||||
|
||||
rounds = 1
|
||||
if config.get('_STEPS') > 0:
|
||||
rounds = math.ceil(total_steps / config.get('_STEPS'))
|
||||
|
||||
logg.info('will perform {} rounds of {} steps'.format(rounds, config.get('_STEPS')))
|
||||
|
||||
last_tx_hash = None
|
||||
for i in range(rounds):
|
||||
signer = settings.get('SIGNER')
|
||||
signer_address = settings.get('SENDER_ADDRESS')
|
||||
|
||||
nonce_oracle = settings.get('NONCE_ORACLE')
|
||||
|
||||
c = DemurrageToken(chain_spec, signer=signer, gas_oracle=gas_oracle, nonce_oracle=nonce_oracle)
|
||||
(tx_hash_hex, o) = c.apply_demurrage(config.get('_EXEC_ADDRESS'), signer_address, limit=config.get('_STEPS'))
|
||||
if settings.get('RPC_SEND'):
|
||||
print(tx_hash_hex)
|
||||
conn.do(o)
|
||||
if config.true('_WAIT_ALL') or (i == rounds - 1 and config.true('_WAIT')):
|
||||
r = conn.wait(tx_hash_hex)
|
||||
if r['status'] == 0:
|
||||
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(o)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
176
python/erc20_demurrage_token/runnable/publish.py
Normal file
176
python/erc20_demurrage_token/runnable/publish.py
Normal file
@ -0,0 +1,176 @@
|
||||
"""Deploy sarafu token
|
||||
|
||||
.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
|
||||
.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
|
||||
|
||||
"""
|
||||
|
||||
# standard imports
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
# external imports
|
||||
import confini
|
||||
from funga.eth.signer import EIP155Signer
|
||||
from funga.eth.keystore.dict import DictKeystore
|
||||
from chainlib.chain import ChainSpec
|
||||
from chainlib.eth.nonce import (
|
||||
RPCNonceOracle,
|
||||
OverrideNonceOracle,
|
||||
)
|
||||
from chainlib.eth.gas import (
|
||||
RPCGasOracle,
|
||||
OverrideGasOracle,
|
||||
)
|
||||
from chainlib.eth.connection import EthHTTPConnection
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
import chainlib.eth.cli
|
||||
from chainlib.eth.cli.arg import (
|
||||
Arg,
|
||||
ArgFlag,
|
||||
process_args,
|
||||
)
|
||||
from chainlib.eth.cli.config import (
|
||||
Config,
|
||||
process_config,
|
||||
)
|
||||
from chainlib.eth.cli.log import process_log
|
||||
from chainlib.eth.settings import process_settings
|
||||
from chainlib.eth.address import to_checksum_address
|
||||
from chainlib.settings import ChainSettings
|
||||
|
||||
from dexif import to_fixed
|
||||
|
||||
# local imports
|
||||
import erc20_demurrage_token
|
||||
from erc20_demurrage_token import (
|
||||
DemurrageToken,
|
||||
DemurrageTokenSettings,
|
||||
)
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
script_dir = os.path.dirname(__file__)
|
||||
data_dir = os.path.join(script_dir, '..', 'data')
|
||||
|
||||
config_dir = os.path.join(data_dir, 'config')
|
||||
|
||||
|
||||
def process_config_local(config, arg, args, flags):
|
||||
config.add(args.token_name, 'TOKEN_NAME')
|
||||
config.add(args.token_symbol, 'TOKEN_SYMBOL')
|
||||
config.add(args.token_decimals, 'TOKEN_DECIMALS')
|
||||
sink_address = to_checksum_address(args.sink_address)
|
||||
config.add(sink_address, 'TOKEN_SINK_ADDRESS')
|
||||
config.add(args.redistribution_period, 'TOKEN_REDISTRIBUTION_PERIOD')
|
||||
|
||||
v = (1 - (args.demurrage_level / 1000000)) ** (1 / config.get('TOKEN_REDISTRIBUTION_PERIOD'))
|
||||
if v >= 1.0:
|
||||
raise ValueError('demurrage level must be less than 100%')
|
||||
demurrage_level = to_fixed(v)
|
||||
logg.info('v {} demurrage level {}'.format(v, demurrage_level))
|
||||
config.add(demurrage_level, 'TOKEN_DEMURRAGE_LEVEL')
|
||||
return config
|
||||
|
||||
|
||||
arg_flags = ArgFlag()
|
||||
arg = Arg(arg_flags)
|
||||
flags = arg_flags.STD_WRITE | arg_flags.WALLET
|
||||
|
||||
argparser = chainlib.eth.cli.ArgumentParser(arg_flags)
|
||||
argparser = process_args(argparser, arg, flags)
|
||||
argparser.add_argument('--name', dest='token_name', type=str, help='Token name')
|
||||
argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol')
|
||||
argparser.add_argument('--decimals', dest='token_decimals', type=int, help='Token decimals')
|
||||
argparser.add_argument('--sink-address', dest='sink_address', type=str, help='demurrage level,ppm per minute')
|
||||
argparser.add_argument('--redistribution-period', type=int, help='redistribution period, minutes (0 = deactivate)') # default 10080 = week
|
||||
argparser.add_argument('--demurrage-level', dest='demurrage_level', type=int, help='demurrage level, ppm per period')
|
||||
args = argparser.parse_args()
|
||||
|
||||
logg = process_log(args, logg)
|
||||
|
||||
config = Config()
|
||||
config = process_config(config, arg, args, flags)
|
||||
config = process_config_local(config, arg, args, flags)
|
||||
logg.debug('config loaded:\n{}'.format(config))
|
||||
|
||||
settings = ChainSettings()
|
||||
settings = process_settings(settings, config)
|
||||
logg.debug('settings loaded:\n{}'.format(settings))
|
||||
|
||||
|
||||
#extra_args = {
|
||||
# 'redistribution_period': 'TOKEN_REDISTRIBUTION_PERIOD',
|
||||
# 'demurrage_level': 'TOKEN_DEMURRAGE_LEVEL',
|
||||
# 'supply_limit': 'TOKEN_SUPPLY_LIMIT',
|
||||
# 'token_name': 'TOKEN_NAME',
|
||||
# 'token_symbol': 'TOKEN_SYMBOL',
|
||||
# 'token_decimals': 'TOKEN_DECIMALS',
|
||||
# 'sink_address': 'TOKEN_SINK_ADDRESS',
|
||||
# 'multi': None,
|
||||
# }
|
||||
#config = chainlib.eth.cli.Config.from_args(args, arg_flags, extra_args=extra_args, default_fee_limit=DemurrageToken.gas(), base_config_dir=config_dir)
|
||||
#
|
||||
#if not bool(config.get('TOKEN_NAME')):
|
||||
# logg.info('token name not set, using symbol {} as name'.format(config.get('TOKEN_SYMBOL')))
|
||||
# config.add(config.get('TOKEN_SYMBOL'), 'TOKEN_NAME', True)
|
||||
#
|
||||
#if config.get('TOKEN_SUPPLY_LIMIT') == None:
|
||||
# config.add(0, 'TOKEN_SUPPLY_LIMIT', True)
|
||||
#
|
||||
#if config.get('TOKEN_REDISTRIBUTION_PERIOD') == None:
|
||||
# config.add(10800, 'TOKEN_REDISTRIBUTION_PERIOD', True)
|
||||
#logg.debug('config loaded:\n{}'.format(config))
|
||||
#
|
||||
#wallet = chainlib.eth.cli.Wallet()
|
||||
#wallet.from_config(config)
|
||||
#
|
||||
#rpc = chainlib.eth.cli.Rpc(wallet=wallet)
|
||||
#conn = rpc.connect_by_config(config)
|
||||
#
|
||||
#chain_spec = ChainSpec.from_chain_str(config.get('CHAIN_SPEC'))
|
||||
|
||||
def main():
|
||||
conn = settings.get('CONN')
|
||||
c = DemurrageToken(
|
||||
settings.get('CHAIN_SPEC'),
|
||||
signer=settings.get('SIGNER'),
|
||||
gas_oracle=settings.get('FEE_ORACLE'),
|
||||
nonce_oracle=settings.get('NONCE_ORACLE'),
|
||||
)
|
||||
token_settings = DemurrageTokenSettings()
|
||||
token_settings.name = config.get('TOKEN_NAME')
|
||||
token_settings.symbol = config.get('TOKEN_SYMBOL')
|
||||
token_settings.decimals = int(config.get('TOKEN_DECIMALS'))
|
||||
token_settings.demurrage_level = int(config.get('TOKEN_DEMURRAGE_LEVEL'))
|
||||
token_settings.period_minutes = int(config.get('TOKEN_REDISTRIBUTION_PERIOD'))
|
||||
token_settings.sink_address = config.get('TOKEN_SINK_ADDRESS')
|
||||
|
||||
(tx_hash_hex, o) = c.constructor(
|
||||
settings.get('SENDER_ADDRESS'),
|
||||
token_settings,
|
||||
)
|
||||
if settings.get('RPC_SEND'):
|
||||
conn.do(o)
|
||||
if config.true('_WAIT'):
|
||||
r = conn.wait(tx_hash_hex)
|
||||
if r['status'] == 0:
|
||||
sys.stderr.write('EVM revert while deploying contract. Wish I had more to tell you')
|
||||
sys.exit(1)
|
||||
# TODO: pass through translator for keys (evm tester uses underscore instead of camelcase)
|
||||
address = r['contractAddress']
|
||||
|
||||
print(address)
|
||||
else:
|
||||
print(tx_hash_hex)
|
||||
|
||||
else:
|
||||
print(o)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
66
python/erc20_demurrage_token/seal.py
Normal file
66
python/erc20_demurrage_token/seal.py
Normal file
@ -0,0 +1,66 @@
|
||||
# standard imports
|
||||
import enum
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.jsonrpc import JSONRPCRequest
|
||||
from chainlib.eth.tx import (
|
||||
TxFactory,
|
||||
TxFormat,
|
||||
)
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
abi_decode_single,
|
||||
)
|
||||
from hexathon import (
|
||||
add_0x,
|
||||
)
|
||||
|
||||
class ContractState(enum.IntEnum):
|
||||
MINTER_STATE = 1
|
||||
SINK_STATE = 2
|
||||
EXPIRY_STATE = 4
|
||||
CAP_STATE = 8
|
||||
|
||||
CONTRACT_SEAL_STATE_MAX = 0
|
||||
|
||||
for v in dir(ContractState):
|
||||
if len(v) > 6 and v[-6:] == '_STATE':
|
||||
CONTRACT_SEAL_STATE_MAX += getattr(ContractState, v).value
|
||||
|
||||
|
||||
class SealedContract(TxFactory):
|
||||
|
||||
def seal(self, contract_address, sender_address, seal, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('seal')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(seal)
|
||||
data = enc.get()
|
||||
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||
tx = self.set_code(tx, data)
|
||||
tx = self.finalize(tx, tx_format)
|
||||
return tx
|
||||
|
||||
|
||||
def is_sealed(self, contract_address, v, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('isSealed')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(v)
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
o['params'].append(self.normalize(tx))
|
||||
o['params'].append('latest')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_is_sealed(self, v):
|
||||
return abi_decode_single(ABIContractType.BOOLEAN, v)
|
@ -10,6 +10,7 @@ from chainlib.eth.tx import (
|
||||
from chainlib.hash import keccak256_string_to_hex
|
||||
from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractDecoder,
|
||||
ABIContractType,
|
||||
abi_decode_single,
|
||||
)
|
||||
@ -20,13 +21,38 @@ from hexathon import (
|
||||
add_0x,
|
||||
strip_0x,
|
||||
)
|
||||
from dexif import from_fixed
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token.data import data_dir
|
||||
from erc20_demurrage_token.seal import SealedContract
|
||||
from erc20_demurrage_token.expiry import ExpiryContract
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DemurrageRedistribution:
|
||||
|
||||
def __init__(self, v):
|
||||
d = ABIContractDecoder()
|
||||
v = strip_0x(v)
|
||||
d.typ(ABIContractType.UINT256)
|
||||
d.typ(ABIContractType.UINT256)
|
||||
d.typ(ABIContractType.BYTES32)
|
||||
d.val(v[:64])
|
||||
d.val(v[64:128])
|
||||
d.val(v[128:192])
|
||||
r = d.decode()
|
||||
|
||||
self.period = r[0]
|
||||
self.value = r[1]
|
||||
self.demurrage = from_fixed(r[2])
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return 'period {} value {} demurrage {}'.format(self.period, self.value, self.demurrage)
|
||||
|
||||
|
||||
class DemurrageTokenSettings:
|
||||
|
||||
def __init__(self):
|
||||
@ -47,59 +73,40 @@ class DemurrageTokenSettings:
|
||||
)
|
||||
|
||||
|
||||
class DemurrageToken(ERC20):
|
||||
class DemurrageToken(ERC20, SealedContract, ExpiryContract):
|
||||
|
||||
__abi = {}
|
||||
__bytecode = {}
|
||||
valid_modes = [
|
||||
'MultiNocap',
|
||||
'SingleNocap',
|
||||
'MultiCap',
|
||||
'SingleCap',
|
||||
]
|
||||
|
||||
def constructor(self, sender_address, settings, redistribute=True, cap=0, tx_format=TxFormat.JSONRPC):
|
||||
if int(cap) < 0:
|
||||
raise ValueError('cap must be 0 or positive integer')
|
||||
code = DemurrageToken.bytecode(multi=redistribute, cap=cap>0)
|
||||
enc = ABIContractEncoder()
|
||||
enc.string(settings.name)
|
||||
enc.string(settings.symbol)
|
||||
enc.uint256(settings.decimals)
|
||||
enc.uint256(settings.demurrage_level)
|
||||
enc.uint256(settings.period_minutes)
|
||||
enc.address(settings.sink_address)
|
||||
if cap > 0:
|
||||
enc.uint256(cap)
|
||||
code += enc.get()
|
||||
def constructor(self, sender_address, settings, tx_format=TxFormat.JSONRPC, version=None):
|
||||
code = self.cargs(settings.name, settings.symbol, settings.decimals, settings.demurrage_level, settings.period_minutes, settings.sink_address, version=version)
|
||||
tx = self.template(sender_address, None, use_nonce=True)
|
||||
tx = self.set_code(tx, code)
|
||||
return self.finalize(tx, tx_format)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def cargs(name, symbol, decimals, demurrage_level, period_minutes, sink_address, version=None):
|
||||
code = DemurrageToken.bytecode()
|
||||
enc = ABIContractEncoder()
|
||||
enc.string(name)
|
||||
enc.string(symbol)
|
||||
enc.uint256(decimals)
|
||||
enc.uint256(demurrage_level)
|
||||
enc.uint256(period_minutes)
|
||||
enc.address(sink_address)
|
||||
code += enc.get()
|
||||
return code
|
||||
|
||||
|
||||
@staticmethod
|
||||
def gas(code=None):
|
||||
return 4000000
|
||||
return 7000000
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __to_contract_name(multi, cap):
|
||||
name = 'DemurrageToken'
|
||||
if multi:
|
||||
name += 'Multi'
|
||||
else:
|
||||
name += 'Single'
|
||||
if cap:
|
||||
name += 'Cap'
|
||||
else:
|
||||
name += 'Nocap'
|
||||
logg.debug('bytecode name {}'.format(name))
|
||||
return name
|
||||
|
||||
|
||||
@staticmethod
|
||||
def abi(multi=True, cap=False):
|
||||
name = DemurrageToken.__to_contract_name(multi, cap)
|
||||
def abi():
|
||||
name = 'DemurrageTokenSingleNocap'
|
||||
if DemurrageToken.__abi.get(name) == None:
|
||||
f = open(os.path.join(data_dir, name + '.json'), 'r')
|
||||
DemurrageToken.__abi[name] = json.load(f)
|
||||
@ -108,8 +115,8 @@ class DemurrageToken(ERC20):
|
||||
|
||||
|
||||
@staticmethod
|
||||
def bytecode(multi=True, cap=False):
|
||||
name = DemurrageToken.__to_contract_name(multi, cap)
|
||||
def bytecode(version=None):
|
||||
name = 'DemurrageTokenSingleNocap'
|
||||
if DemurrageToken.__bytecode.get(name) == None:
|
||||
f = open(os.path.join(data_dir, name + '.bin'), 'r')
|
||||
DemurrageToken.__bytecode[name] = f.read()
|
||||
@ -145,9 +152,14 @@ class DemurrageToken(ERC20):
|
||||
return tx
|
||||
|
||||
|
||||
# backwards compatibility
|
||||
def add_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
return self.add_writer(contract_address, sender_address, address, tx_format=tx_format)
|
||||
|
||||
|
||||
def add_writer(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('addMinter')
|
||||
enc.method('addWriter')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
data = enc.get()
|
||||
@ -157,9 +169,26 @@ class DemurrageToken(ERC20):
|
||||
return tx
|
||||
|
||||
|
||||
def remove_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
def set_max_supply(self, contract_address, sender_address, cap, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('removeMinter')
|
||||
enc.method('setMaxSupply')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(cap)
|
||||
data = enc.get()
|
||||
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||
tx = self.set_code(tx, data)
|
||||
tx = self.finalize(tx, tx_format)
|
||||
return tx
|
||||
|
||||
|
||||
# backwards compatibility
|
||||
def remove_minter(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
return self.delete_writer(contract_address, sender_address, address, tx_format=tx_format)
|
||||
|
||||
|
||||
def delete_writer(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('deleteWriter')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
data = enc.get()
|
||||
@ -280,18 +309,18 @@ class DemurrageToken(ERC20):
|
||||
return o
|
||||
|
||||
|
||||
def to_redistribution(self, contract_address, participants, demurrage_modifier_ppm, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
def to_redistribution(self, contract_address, participants, demurrage_modifier, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toRedistribution')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ_literal('int128')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(participants)
|
||||
enc.uint256(demurrage_modifier_ppm)
|
||||
enc.uint256(demurrage_modifier)
|
||||
enc.uint256(value)
|
||||
enc.uint256(period)
|
||||
data = add_0x(enc.get())
|
||||
@ -310,8 +339,11 @@ class DemurrageToken(ERC20):
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toRedistributionPeriod')
|
||||
enc.typ(ABIContractType.BYTES32)
|
||||
enc.bytes32(redistribution)
|
||||
v = strip_0x(redistribution)
|
||||
enc.typ_literal('(uint32,uint72,uint64)')
|
||||
enc.bytes32(v[:64])
|
||||
enc.bytes32(v[64:128])
|
||||
enc.bytes32(v[128:192])
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
@ -321,22 +353,26 @@ class DemurrageToken(ERC20):
|
||||
return o
|
||||
|
||||
|
||||
def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toRedistributionParticipants')
|
||||
enc.typ(ABIContractType.BYTES32)
|
||||
enc.bytes32(redistribution)
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
o['params'].append(self.normalize(tx))
|
||||
o['params'].append('latest')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
# def to_redistribution_participants(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
# j = JSONRPCRequest(id_generator)
|
||||
# o = j.template()
|
||||
# o['method'] = 'eth_call'
|
||||
# enc = ABIContractEncoder()
|
||||
# enc.method('toRedistributionParticipants')
|
||||
# v = strip_0x(redistribution)
|
||||
# enc.typ_literal('(uint32,uint72,uint104)')
|
||||
# #enc.typ(ABIContractType.BYTES32)
|
||||
# enc.bytes32(v[:64])
|
||||
# enc.bytes32(v[64:128])
|
||||
# enc.bytes32(v[128:192])
|
||||
# data = add_0x(enc.get())
|
||||
# tx = self.template(sender_address, contract_address)
|
||||
# tx = self.set_code(tx, data)
|
||||
# o['params'].append(self.normalize(tx))
|
||||
# o['params'].append('latest')
|
||||
# o = j.finalize(o)
|
||||
# return o
|
||||
#
|
||||
|
||||
def to_redistribution_supply(self, contract_address, redistribution, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
@ -344,8 +380,11 @@ class DemurrageToken(ERC20):
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toRedistributionSupply')
|
||||
enc.typ(ABIContractType.BYTES32)
|
||||
enc.bytes32(redistribution)
|
||||
v = strip_0x(redistribution)
|
||||
enc.typ_literal('(uint32,uint72,uint64)')
|
||||
enc.bytes32(v[:64])
|
||||
enc.bytes32(v[64:128])
|
||||
enc.bytes32(v[128:192])
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
@ -361,8 +400,11 @@ class DemurrageToken(ERC20):
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('toRedistributionDemurrageModifier')
|
||||
enc.typ(ABIContractType.BYTES32)
|
||||
enc.bytes32(redistribution)
|
||||
v = strip_0x(redistribution)
|
||||
enc.typ_literal('(uint32,uint72,uint64)')
|
||||
enc.bytes32(v[:64])
|
||||
enc.bytes32(v[64:128])
|
||||
enc.bytes32(v[128:192])
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
@ -389,6 +431,31 @@ class DemurrageToken(ERC20):
|
||||
return o
|
||||
|
||||
|
||||
def set_sink_address(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('setSinkAddress')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(address)
|
||||
data = enc.get()
|
||||
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||
tx = self.set_code(tx, data)
|
||||
tx = self.finalize(tx, tx_format)
|
||||
return tx
|
||||
|
||||
|
||||
def sweep(self, contract_address, sender_address, recipient_address, tx_format=TxFormat.JSONRPC):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('sweep')
|
||||
enc.typ(ABIContractType.ADDRESS)
|
||||
enc.address(recipient_address)
|
||||
data = enc.get()
|
||||
tx = self.template(sender_address, contract_address, use_nonce=True)
|
||||
tx = self.set_code(tx, data)
|
||||
tx = self.finalize(tx, tx_format)
|
||||
return tx
|
||||
|
||||
|
||||
|
||||
def apply_demurrage(self, contract_address, sender_address, limit=0, tx_format=TxFormat.JSONRPC):
|
||||
if limit == 0:
|
||||
return self.transact_noarg('applyDemurrage', contract_address, sender_address)
|
||||
@ -420,8 +487,8 @@ class DemurrageToken(ERC20):
|
||||
return tx
|
||||
|
||||
|
||||
def tax_level(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('taxLevel', contract_address, sender_address=sender_address)
|
||||
def decay_level(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('decayLevel', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def resolution_factor(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
@ -448,28 +515,28 @@ class DemurrageToken(ERC20):
|
||||
return self.call_noarg('demurrageTimestamp', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def supply_cap(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('supplyCap', contract_address, sender_address=sender_address)
|
||||
def max_supply(self, contract_address, sender_address=ZERO_ADDRESS):
|
||||
return self.call_noarg('maxSupply', contract_address, sender_address=sender_address)
|
||||
|
||||
|
||||
def grow_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
o = j.template()
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('growBy')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.uint256(value)
|
||||
enc.uint256(period)
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
o['params'].append(self.normalize(tx))
|
||||
o['params'].append('latest')
|
||||
o = j.finalize(o)
|
||||
return o
|
||||
|
||||
# def grow_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
# j = JSONRPCRequest(id_generator)
|
||||
# o = j.template()
|
||||
# o['method'] = 'eth_call'
|
||||
# enc = ABIContractEncoder()
|
||||
# enc.method('growBy')
|
||||
# enc.typ(ABIContractType.UINT256)
|
||||
# enc.typ(ABIContractType.UINT256)
|
||||
# enc.uint256(value)
|
||||
# enc.uint256(period)
|
||||
# data = add_0x(enc.get())
|
||||
# tx = self.template(sender_address, contract_address)
|
||||
# tx = self.set_code(tx, data)
|
||||
# o['params'].append(self.normalize(tx))
|
||||
# o['params'].append('latest')
|
||||
# o = j.finalize(o)
|
||||
# return o
|
||||
#
|
||||
|
||||
def decay_by(self, contract_address, value, period, sender_address=ZERO_ADDRESS, id_generator=None):
|
||||
j = JSONRPCRequest(id_generator)
|
||||
@ -497,7 +564,7 @@ class DemurrageToken(ERC20):
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('getDistribution')
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ(ABIContractType.UINT256)
|
||||
enc.typ_literal('int128')
|
||||
enc.uint256(supply)
|
||||
enc.uint256(demurrage_amount)
|
||||
data = add_0x(enc.get())
|
||||
@ -515,8 +582,11 @@ class DemurrageToken(ERC20):
|
||||
o['method'] = 'eth_call'
|
||||
enc = ABIContractEncoder()
|
||||
enc.method('getDistributionFromRedistribution')
|
||||
enc.typ(ABIContractType.BYTES32)
|
||||
enc.bytes32(redistribution)
|
||||
v = strip_0x(redistribution)
|
||||
enc.typ_literal('(uint32,uint72,uint64)')
|
||||
enc.bytes32(v[:64])
|
||||
enc.bytes32(v[64:128])
|
||||
enc.bytes32(v[128:192])
|
||||
data = add_0x(enc.get())
|
||||
tx = self.template(sender_address, contract_address)
|
||||
tx = self.set_code(tx, data)
|
||||
@ -544,7 +614,8 @@ class DemurrageToken(ERC20):
|
||||
|
||||
@classmethod
|
||||
def parse_demurrage_amount(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
# return abi_decode_single(ABIContractType.UINT256, v)
|
||||
return from_fixed(v)
|
||||
|
||||
|
||||
@classmethod
|
||||
@ -559,8 +630,8 @@ class DemurrageToken(ERC20):
|
||||
|
||||
@classmethod
|
||||
def parse_redistributions(self, v):
|
||||
return abi_decode_single(ABIContractType.BYTES32, v)
|
||||
|
||||
return strip_0x(v)
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_account_period(self, v):
|
||||
@ -598,7 +669,7 @@ class DemurrageToken(ERC20):
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse_tax_level(self, v):
|
||||
def parse_decay_level(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
@ -610,3 +681,26 @@ class DemurrageToken(ERC20):
|
||||
@classmethod
|
||||
def parse_total_burned(self, v):
|
||||
return abi_decode_single(ABIContractType.UINT256, v)
|
||||
|
||||
|
||||
def bytecode(**kwargs):
|
||||
return DemurrageToken.bytecode(version=kwargs.get('version'))
|
||||
|
||||
|
||||
def create(**kwargs):
|
||||
return DemurrageToken.cargs(
|
||||
kwargs['name'],
|
||||
kwargs['symbol'],
|
||||
kwargs['decimals'],
|
||||
kwargs['demurragelevel'],
|
||||
kwargs['redistributionperiod'],
|
||||
kwargs['sinkaddress'],
|
||||
version=kwargs.get('version'))
|
||||
|
||||
|
||||
def args(v):
|
||||
if v == 'create':
|
||||
return (['name', 'symbol', 'decimals', 'demurragelevel', 'redistributionperiod', 'sinkaddress'], ['version'],)
|
||||
elif v == 'default' or v == 'bytecode':
|
||||
return ([], ['version'],)
|
||||
raise ValueError('unknown command: ' + v)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import os
|
||||
import math
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.unittest.ethtester import EthTesterCase
|
||||
@ -19,20 +20,21 @@ from erc20_demurrage_token import (
|
||||
DemurrageTokenSettings,
|
||||
DemurrageToken,
|
||||
)
|
||||
from dexif import *
|
||||
|
||||
logg = logging.getLogger(__name__)
|
||||
logg = logging.getLogger()
|
||||
|
||||
#BLOCKTIME = 5 # seconds
|
||||
TAX_LEVEL = int(10000 * 2) # 2%
|
||||
# calc "1-(0.98)^(1/518400)" <- 518400 = 30 days of blocks
|
||||
# 0.00000003897127107225
|
||||
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
|
||||
PERIOD = 10
|
||||
PERIOD = 43200
|
||||
|
||||
|
||||
class TestTokenDeploy:
|
||||
|
||||
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, supply=10**12, tax_level=TAX_LEVEL, period=PERIOD):
|
||||
"""tax level is ppm, 1000000 = 100%"""
|
||||
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, tax_level=TAX_LEVEL, period=PERIOD):
|
||||
self.tax_level = tax_level
|
||||
self.period_seconds = period * 60
|
||||
|
||||
@ -40,7 +42,8 @@ class TestTokenDeploy:
|
||||
self.settings.name = token_name
|
||||
self.settings.symbol = token_symbol
|
||||
self.settings.decimals = 6
|
||||
self.settings.demurrage_level = tax_level * (10 ** 32)
|
||||
tax_level_input = to_fixed((1 - (tax_level / 1000000)) ** (1 / period))
|
||||
self.settings.demurrage_level = tax_level_input
|
||||
self.settings.period_minutes = period
|
||||
self.settings.sink_address = sink_address
|
||||
self.sink_address = self.settings.sink_address
|
||||
@ -57,25 +60,11 @@ class TestTokenDeploy:
|
||||
except TypeError:
|
||||
self.start_time = int(r['timestamp'])
|
||||
|
||||
self.default_supply = supply
|
||||
self.default_supply_cap = int(self.default_supply * 10)
|
||||
|
||||
|
||||
def deploy(self, rpc, deployer_address, interface, mode, supply_cap=10**12):
|
||||
def publish(self, rpc, publisher_address, interface, supply_cap=0):
|
||||
tx_hash = None
|
||||
o = None
|
||||
logg.debug('mode {} {}'.format(mode, self.settings))
|
||||
self.mode = mode
|
||||
if mode == 'MultiNocap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=True, cap=0)
|
||||
elif mode == 'SingleNocap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=0)
|
||||
elif mode == 'MultiCap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=True, cap=supply_cap)
|
||||
elif mode == 'SingleCap':
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=supply_cap)
|
||||
else:
|
||||
raise ValueError('Invalid mode "{}", valid are {}'.format(mode, DemurrageToken.valid_modes))
|
||||
(tx_hash, o) = interface.constructor(publisher_address, self.settings)
|
||||
|
||||
r = rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
@ -100,25 +89,43 @@ class TestDemurrage(EthTesterCase):
|
||||
period = getattr(self, 'period')
|
||||
except AttributeError as e:
|
||||
pass
|
||||
self.deployer = TestTokenDeploy(self.rpc, period=period)
|
||||
self.default_supply = self.deployer.default_supply
|
||||
self.default_supply_cap = self.deployer.default_supply_cap
|
||||
self.publisher = TestTokenDeploy(self.rpc, period=period, sink_address=self.accounts[9])
|
||||
self.default_supply = 0
|
||||
self.default_supply_cap = 0
|
||||
self.start_block = None
|
||||
self.address = None
|
||||
self.start_time = None
|
||||
|
||||
|
||||
def deploy(self, interface, mode):
|
||||
self.address = self.deployer.deploy(self.rpc, self.accounts[0], interface, mode, supply_cap=self.default_supply_cap)
|
||||
self.start_block = self.deployer.start_block
|
||||
self.start_time = self.deployer.start_time
|
||||
self.tax_level = self.deployer.tax_level
|
||||
self.period_seconds = self.deployer.period_seconds
|
||||
self.sink_address = self.deployer.sink_address
|
||||
def publish(self, interface):
|
||||
self.address = self.publisher.publish(self.rpc, self.accounts[0], interface, supply_cap=self.default_supply_cap)
|
||||
self.start_block = self.publisher.start_block
|
||||
self.start_time = self.publisher.start_time
|
||||
self.tax_level = self.publisher.tax_level
|
||||
self.period_seconds = self.publisher.period_seconds
|
||||
self.sink_address = self.publisher.sink_address
|
||||
|
||||
logg.debug('contract address {} start block {} start time {}'.format(self.address, self.start_block, self.start_time))
|
||||
|
||||
|
||||
def assert_within(self, v, target, tolerance_ppm):
|
||||
lower_target = target - (target * (tolerance_ppm / 1000000))
|
||||
higher_target = target + (target * (tolerance_ppm / 1000000))
|
||||
#self.assertGreaterEqual(v, lower_target)
|
||||
#self.assertLessEqual(v, higher_target)
|
||||
if v >= lower_target and v <= higher_target:
|
||||
logg.debug('asserted within {} <= {} <= {}'.format(lower_target, v, higher_target))
|
||||
return
|
||||
raise AssertionError('{} not within lower {} and higher {}'.format(v, lower_target, higher_target))
|
||||
|
||||
|
||||
def assert_within_greater(self, v, target, tolerance_ppm):
|
||||
greater_target = target + (target * (tolerance_ppm / 1000000))
|
||||
self.assertLessEqual(v, greater_target)
|
||||
self.assertGreaterEqual(v, target)
|
||||
logg.debug('asserted within greater {} >= {} >= {}'.format(greater_target, v, target))
|
||||
|
||||
|
||||
def assert_within_lower(self, v, target, tolerance_ppm):
|
||||
lower_target = target - (target * (tolerance_ppm / 1000000))
|
||||
self.assertGreaterEqual(v, lower_target)
|
||||
@ -126,11 +133,10 @@ class TestDemurrage(EthTesterCase):
|
||||
logg.debug('asserted within lower {} <= {} <= {}'.format(lower_target, v, target))
|
||||
|
||||
|
||||
def assert_within_greater(self, v, target, tolerance_ppm):
|
||||
higher_target = target + (target * (tolerance_ppm / 1000000))
|
||||
self.assertLessEqual(v, higher_target)
|
||||
self.assertGreaterEqual(v, target)
|
||||
logg.debug('asserted within lower {} <= {} <= {}'.format(target, v, higher_target))
|
||||
def assert_equal_decimals(self, v, target, precision):
|
||||
target = int(target * (10 ** precision))
|
||||
target = target / (10 ** precision)
|
||||
self.assertEqual(v, target)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
@ -145,115 +151,7 @@ class TestDemurrageDefault(TestDemurrage):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
|
||||
if self.mode == None:
|
||||
self.mode = 'MultiNocap'
|
||||
logg.debug('executing test setup default mode {}'.format(self.mode))
|
||||
self.publish(c)
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
|
||||
class TestDemurrageSingle(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrageSingle, self).setUp()
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
|
||||
single_valid_modes = [
|
||||
'SingleNocap',
|
||||
'SingleCap',
|
||||
]
|
||||
if self.mode != None:
|
||||
if self.mode not in single_valid_modes:
|
||||
raise ValueError('Invalid mode "{}" for "single" contract tests, valid are {}'.format(self.mode, single_valid_modes))
|
||||
else:
|
||||
self.mode = 'SingleNocap'
|
||||
logg.debug('executing test setup demurragesingle mode {}'.format(self.mode))
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
|
||||
class TestDemurrageCap(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrageCap, self).setUp()
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
|
||||
cap_valid_modes = [
|
||||
'MultiCap',
|
||||
'SingleCap',
|
||||
]
|
||||
if self.mode != None:
|
||||
if self.mode not in cap_valid_modes:
|
||||
raise ValueError('Invalid mode "{}" for "cap" contract tests, valid are {}'.format(self.mode, cap_valid_modes))
|
||||
else:
|
||||
self.mode = 'MultiCap'
|
||||
logg.debug('executing test setup demurragecap mode {}'.format(self.mode))
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
|
||||
|
||||
class TestDemurrageUnit(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
self.period = 1
|
||||
self.period_seconds = self.period * 60
|
||||
self.tax_level = TAX_LEVEL
|
||||
|
||||
super(TestDemurrageUnit, self).setUp()
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
self.settings = DemurrageTokenSettings()
|
||||
self.settings.name = 'Foo Token'
|
||||
self.settings.symbol = 'FOO'
|
||||
self.settings.decimals = 6
|
||||
self.settings.demurrage_level = self.tax_level * (10 ** 32)
|
||||
self.settings.period_minutes = int(self.period_seconds/60)
|
||||
self.settings.sink_address = self.accounts[9]
|
||||
self.sink_address = self.settings.sink_address
|
||||
|
||||
o = block_latest()
|
||||
self.start_block = self.rpc.do(o)
|
||||
|
||||
o = block_by_number(self.start_block, include_tx=False)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
try:
|
||||
self.start_time = int(r['timestamp'], 16)
|
||||
except TypeError:
|
||||
self.start_time = int(r['timestamp'])
|
||||
|
||||
self.default_supply = 1000000000000
|
||||
self.default_supply_cap = int(self.default_supply * 10)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
|
||||
unit_valid_modes = [
|
||||
'SingleNocap',
|
||||
'SingleCap',
|
||||
]
|
||||
if self.mode != None:
|
||||
if self.mode not in unit_valid_modes:
|
||||
raise ValueError('Invalid mode "{}" for "unit" contract tests, valid are {}'.format(self.mode, unit_valid_modes))
|
||||
else:
|
||||
self.mode = 'SingleNocap'
|
||||
logg.debug('executing test setup unit mode {}'.format(self.mode))
|
||||
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
self.default_supply = 10**12
|
||||
self.default_supply_cap = self.default_supply
|
||||
|
146
python/erc20_demurrage_token/unittest/newbase.py
Normal file
146
python/erc20_demurrage_token/unittest/newbase.py
Normal file
@ -0,0 +1,146 @@
|
||||
# standard imports
|
||||
import logging
|
||||
import os
|
||||
import math
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.unittest.ethtester import EthTesterCase
|
||||
from chainlib.eth.tx import (
|
||||
receipt,
|
||||
)
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
)
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import (
|
||||
DemurrageTokenSettings,
|
||||
DemurrageToken,
|
||||
)
|
||||
from dexif import *
|
||||
|
||||
logg = logging.getLogger()
|
||||
|
||||
TAX_LEVEL = int(10000 * 2) # 2%
|
||||
PERIOD = 43200 # 30 days in minutes
|
||||
|
||||
class TestTokenDeploy:
|
||||
|
||||
"""tax level is ppm, 1000000 = 100%"""
|
||||
def __init__(self, rpc, token_symbol='FOO', token_name='Foo Token', sink_address=ZERO_ADDRESS, supply=10**12, tax_level=TAX_LEVEL, period=PERIOD):
|
||||
self.tax_level = tax_level
|
||||
self.period_seconds = period * 60
|
||||
|
||||
self.settings = DemurrageTokenSettings()
|
||||
self.settings.name = token_name
|
||||
self.settings.symbol = token_symbol
|
||||
self.settings.decimals = 6
|
||||
tax_level_input = to_fixed((1 - (tax_level / 1000000)) ** (1 / period))
|
||||
self.settings.demurrage_level = tax_level_input
|
||||
self.settings.period_minutes = period
|
||||
self.settings.sink_address = sink_address
|
||||
self.sink_address = self.settings.sink_address
|
||||
logg.debug('using demurrage token settings: {}'.format(self.settings))
|
||||
|
||||
o = block_latest()
|
||||
self.start_block = rpc.do(o)
|
||||
|
||||
o = block_by_number(self.start_block, include_tx=False)
|
||||
r = rpc.do(o)
|
||||
|
||||
try:
|
||||
self.start_time = int(r['timestamp'], 16)
|
||||
except TypeError:
|
||||
self.start_time = int(r['timestamp'])
|
||||
|
||||
self.default_supply = supply
|
||||
self.default_supply_cap = 0
|
||||
|
||||
|
||||
def deploy(self, rpc, deployer_address, interface, supply_cap=0):
|
||||
tx_hash = None
|
||||
o = None
|
||||
(tx_hash, o) = interface.constructor(deployer_address, self.settings, redistribute=False, cap=0)
|
||||
|
||||
r = rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = rpc.do(o)
|
||||
assert r['status'] == 1
|
||||
self.start_block = r['block_number']
|
||||
self.address = r['contract_address']
|
||||
|
||||
o = block_by_number(r['block_number'])
|
||||
r = rpc.do(o)
|
||||
self.start_time = r['timestamp']
|
||||
|
||||
return self.address
|
||||
|
||||
|
||||
class TestDemurrage(EthTesterCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrage, self).setUp()
|
||||
period = PERIOD
|
||||
try:
|
||||
period = getattr(self, 'period')
|
||||
except AttributeError as e:
|
||||
pass
|
||||
self.deployer = TestTokenDeploy(self.rpc, period=period)
|
||||
self.default_supply = self.deployer.default_supply
|
||||
self.default_supply_cap = self.deployer.default_supply_cap
|
||||
self.start_block = None
|
||||
self.address = None
|
||||
self.start_time = None
|
||||
|
||||
|
||||
def deploy(self, interface):
|
||||
self.address = self.deployer.deploy(self.rpc, self.accounts[0], interface, supply_cap=self.default_supply_cap)
|
||||
self.start_block = self.deployer.start_block
|
||||
self.start_time = self.deployer.start_time
|
||||
self.tax_level = self.deployer.tax_level
|
||||
self.period_seconds = self.deployer.period_seconds
|
||||
self.sink_address = self.deployer.sink_address
|
||||
|
||||
logg.debug('contract address {} start block {} start time {}'.format(self.address, self.start_block, self.start_time))
|
||||
|
||||
|
||||
def assert_within(self, v, target, tolerance_ppm):
|
||||
lower_target = target - (target * (tolerance_ppm / 1000000))
|
||||
higher_target = target + (target * (tolerance_ppm / 1000000))
|
||||
#self.assertGreaterEqual(v, lower_target)
|
||||
#self.assertLessEqual(v, higher_target)
|
||||
if v >= lower_target and v <= higher_target:
|
||||
logg.debug('asserted within {} <= {} <= {}'.format(lower_target, v, higher_target))
|
||||
return
|
||||
raise AssertionError('{} not within lower {} and higher {}'.format(v, lower_target, higher_target))
|
||||
|
||||
|
||||
def assert_within_lower(self, v, target, tolerance_ppm):
|
||||
lower_target = target - (target * (tolerance_ppm / 1000000))
|
||||
self.assertGreaterEqual(v, lower_target)
|
||||
self.assertLessEqual(v, target)
|
||||
logg.debug('asserted within lower {} <= {} <= {}'.format(lower_target, v, target))
|
||||
|
||||
|
||||
def assert_equal_decimals(self, v, target, precision):
|
||||
target = int(target * (10 ** precision))
|
||||
target = target / (10 ** precision)
|
||||
self.assertEqual(v, target)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestDemurrageDefault(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurrageDefault, self).setUp()
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.deploy(c)
|
@ -1,3 +1,5 @@
|
||||
chainlib-eth~=0.4.6
|
||||
eth-erc20~=0.5.1
|
||||
chainlib-eth~=0.4.11
|
||||
chainlib~=0.4.8
|
||||
eth-erc20~=0.5.5
|
||||
funga-eth~=0.6.0
|
||||
dexif~=0.0.2
|
||||
|
@ -1,44 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -x
|
||||
set -a
|
||||
set -e
|
||||
|
||||
export PYTHONPATH=.
|
||||
|
||||
#modes=(MultiNocap MultiCap SingleCap SingleNocap)
|
||||
#modes=(SingleCap SingleNocap) # other contracts need to be updted
|
||||
modes=(SingleNocap) # other contracts need to be updted
|
||||
for m in ${modes[@]}; do
|
||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_basic.py
|
||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_growth.py
|
||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_amounts.py
|
||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_single.py
|
||||
ERC20_DEMURRAGE_TOKEN_TEST_MODE=$m python tests/test_burn.py
|
||||
set -x
|
||||
default_pythonpath=$PYTHONPATH:.
|
||||
export PYTHONPATH=${default_pythonpath:-.}
|
||||
>&2 echo using pythonpath $PYTHONPATH
|
||||
for f in `ls tests/test_*.py`; do
|
||||
python $f
|
||||
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
|
||||
|
||||
set +e
|
||||
set +x
|
||||
set +e
|
||||
set +a
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = erc20-demurrage-token
|
||||
version = 0.2.0
|
||||
version = 0.4.2
|
||||
description = ERC20 token with redistributed continual demurrage
|
||||
author = Louis Holbrook
|
||||
author_email = dev@holbrook.no
|
||||
@ -40,5 +40,4 @@ packages =
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
erc20-demurrage-token-deploy = erc20_demurrage_token.runnable.deploy:main
|
||||
erc20-demurrage-token-apply = erc20_demurrage_token.runnable.apply:main
|
||||
erc20-demurrage-token-publish = erc20_demurrage_token.runnable.publish:main
|
||||
|
167
python/tests/bench_gas.py
Normal file
167
python/tests/bench_gas.py
Normal file
@ -0,0 +1,167 @@
|
||||
# standard imports
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
|
||||
class BenchBasic(TestDemurrageDefault):
|
||||
|
||||
def setUp(self):
|
||||
super(BenchBasic, self).setUp()
|
||||
self.bench = {
|
||||
'mint': None,
|
||||
'transfer_light': None,
|
||||
'transfer_heavy': None,
|
||||
'approve': None,
|
||||
'transfer_from': None,
|
||||
'period_light': None,
|
||||
'period_heavy': None,
|
||||
'period_catchup': None,
|
||||
'demurrage': None,
|
||||
}
|
||||
|
||||
|
||||
def test_bench_min(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
self.bench['mint'] = r['gas_used']
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer(self.address, self.accounts[1], self.accounts[2], 512)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
self.bench['transfer_light'] = r['gas_used']
|
||||
|
||||
(tx_hash, o) = c.approve(self.address, self.accounts[1], self.accounts[0], 512)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
self.bench['approve'] = r['gas_used']
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer_from(self.address, self.accounts[0], self.accounts[1], self.accounts[3], 256)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
self.bench['transfer_from'] = r['gas_used']
|
||||
|
||||
z = 0
|
||||
for i in range(100):
|
||||
self.backend.time_travel(self.start_time + int(self.period_seconds / 2) + (10 * (i * (i + 1))))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
z += r['gas_used']
|
||||
logg.info('demurrage round {} gas {}'.format(i, r['gas_used']))
|
||||
z /= 100
|
||||
self.bench['demurrage'] = int(z)
|
||||
|
||||
z = 0
|
||||
for i in range(100):
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * (i + 1)))
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
z += r['gas_used']
|
||||
logg.info('period with demurrage round {} gas {}'.format(i, r['gas_used']))
|
||||
|
||||
z /= 100
|
||||
self.bench['period_heavy'] = int(z)
|
||||
|
||||
z = 0
|
||||
for i in range(100):
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * ((i + 101))))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
z += r['gas_used']
|
||||
logg.info('period without demurrage round {} gas {}'.format(i, r['gas_used']))
|
||||
|
||||
z /= 100
|
||||
self.bench['period_light'] = int(z)
|
||||
|
||||
z = 0
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * 401))
|
||||
for i in range(100):
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
z += r['gas_used']
|
||||
logg.info('period catchup round {} gas {}'.format(i, r['gas_used']))
|
||||
|
||||
z /= 100
|
||||
self.bench['period_catchup'] = int(z)
|
||||
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * 501))
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[2], 1024)
|
||||
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 * 502))
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[2], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer(self.address, self.accounts[2], self.accounts[4], 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
self.bench['transfer_heavy'] = r['gas_used']
|
||||
|
||||
print(json.dumps(self.bench))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -13,7 +13,7 @@ from chainlib.eth.tx import receipt
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -27,6 +27,9 @@ class TestAmounts(TestDemurrageDefault):
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1000)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds)
|
||||
|
||||
@ -36,7 +39,7 @@ class TestAmounts(TestDemurrageDefault):
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, 817)
|
||||
self.assertEqual(balance, 980)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1000)
|
||||
r = self.rpc.do(o)
|
||||
@ -44,7 +47,7 @@ class TestAmounts(TestDemurrageDefault):
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assert_within_lower(balance, 1817, 750)
|
||||
self.assert_within_lower(balance, 1980, 750)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds * 2)
|
||||
|
||||
@ -57,8 +60,8 @@ class TestAmounts(TestDemurrageDefault):
|
||||
|
||||
expected_balance = ((1 - self.tax_level / 1000000) ** 10) * 1000
|
||||
expected_balance += ((1 - self.tax_level / 1000000) ** 20) * 1000
|
||||
self.assert_within_lower(balance, expected_balance, 500)
|
||||
|
||||
#self.assert_within_lower(balance, expected_balance, 500)
|
||||
self.assertEqual(balance, 1940)
|
||||
|
||||
def test_transfers(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
@ -74,7 +77,7 @@ class TestAmounts(TestDemurrageDefault):
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, 1634)
|
||||
self.assertEqual(balance, 1960)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
@ -84,7 +87,7 @@ class TestAmounts(TestDemurrageDefault):
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, 1134)
|
||||
self.assertEqual(balance, 1460)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
@ -99,25 +102,46 @@ class TestAmounts(TestDemurrageDefault):
|
||||
r = self.rpc.do(o)
|
||||
|
||||
cases = [
|
||||
(61, 1960),
|
||||
(121, 1920),
|
||||
(181, 1882),
|
||||
(241, 1844),
|
||||
(301, 1807),
|
||||
(361, 1771),
|
||||
(421, 1736),
|
||||
(481, 1701),
|
||||
(541, 1667),
|
||||
(601, 1634),
|
||||
(60, 1960),
|
||||
(120, 1920),
|
||||
(180, 1882),
|
||||
(240, 1844),
|
||||
(300, 1807),
|
||||
(360, 1771),
|
||||
(420, 1736),
|
||||
(480, 1701),
|
||||
(540, 1667),
|
||||
(600, 1634),
|
||||
]
|
||||
for case in cases:
|
||||
self.backend.time_travel(self.start_time + case[0])
|
||||
self.backend.time_travel(self.start_time + int(case[0] * (self.period_seconds / 60)))
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, case[1])
|
||||
self.assert_within_lower(balance, case[1], 10000)
|
||||
|
||||
|
||||
def test_sweep(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[0], 2000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
(tx_hash, o) = c.sweep(self.address, self.accounts[0], self.accounts[1])
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(c.parse_balance(r), 0)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assert_within(c.parse_balance(r), 2000, 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -18,7 +18,7 @@ from chainlib.eth.block import (
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -55,7 +55,8 @@ class TestBasic(TestDemurrageDefault):
|
||||
|
||||
|
||||
def test_apply_demurrage_limited(self):
|
||||
modifier = (10 ** 28)
|
||||
#modifier = (10 ** 28)
|
||||
modifier = 1
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
@ -65,8 +66,8 @@ class TestBasic(TestDemurrageDefault):
|
||||
demurrage_amount = c.parse_demurrage_amount(r)
|
||||
self.assertEqual(modifier, demurrage_amount)
|
||||
|
||||
self.backend.time_travel(self.start_time + 120)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0], limit=1)
|
||||
self.backend.time_travel(self.start_time + (60 * 43200))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0], limit=20000)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
@ -75,13 +76,12 @@ class TestBasic(TestDemurrageDefault):
|
||||
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage_amount = c.parse_demurrage_amount(r)
|
||||
modifier_base = 1000000 - self.tax_level
|
||||
modifier = int(modifier_base * (10 ** 22)) # 38 decimal places minus 6 (1000000)
|
||||
self.assertEqual(modifier, demurrage_amount)
|
||||
self.assert_equal_decimals(0.9906, demurrage_amount, 4)
|
||||
|
||||
|
||||
def test_apply_demurrage(self):
|
||||
modifier = (10 ** 28)
|
||||
#modifier = (10 ** 28)
|
||||
modifier = 1
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
@ -97,7 +97,7 @@ class TestBasic(TestDemurrageDefault):
|
||||
b = self.rpc.do(o)
|
||||
logg.debug('block {} start {}'.format(b['timestamp'], self.start_time))
|
||||
|
||||
self.backend.time_travel(self.start_time + 2)
|
||||
self.backend.time_travel(self.start_time + (60 * 43200))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
@ -107,9 +107,9 @@ class TestBasic(TestDemurrageDefault):
|
||||
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage_amount = c.parse_demurrage_amount(r)
|
||||
self.assertEqual(modifier, demurrage_amount)
|
||||
self.assert_equal_decimals(0.98, demurrage_amount, 2)
|
||||
|
||||
self.backend.time_travel(self.start_time + 61)
|
||||
self.backend.time_travel(self.start_time + (60 * 43200 * 2))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
@ -120,28 +120,10 @@ class TestBasic(TestDemurrageDefault):
|
||||
demurrage_amount = c.parse_demurrage_amount(r)
|
||||
modifier_base = 1000000 - self.tax_level
|
||||
modifier = int(modifier_base * (10 ** 22)) # 38 decimal places minus 6 (1000000)
|
||||
self.assertEqual(modifier, demurrage_amount)
|
||||
|
||||
self.backend.time_travel(self.start_time + 601)
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
o = c.demurrage_amount(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage_amount = c.parse_demurrage_amount(r)
|
||||
modifier_base = ((1000000 - self.tax_level) / 1000000) ** 10
|
||||
logg.warning('mod base {}'.format(modifier_base))
|
||||
modifier = int(modifier_base * (10 ** 12))
|
||||
|
||||
rounding_tolerance_nano = 4000000 # 0.000004% precision
|
||||
demurrage_amount_truncate = int(demurrage_amount / (10 ** 16)) # equals 38 decimal places - 14 for the modifier magniture - 2 for percent int calc + 6 for token decimals <- TODO verify this calc
|
||||
self.assertGreaterEqual(modifier, demurrage_amount_truncate - rounding_tolerance_nano)
|
||||
self.assertLessEqual(modifier, demurrage_amount_truncate)
|
||||
self.assert_equal_decimals(0.9604, demurrage_amount, 4)
|
||||
|
||||
|
||||
def test_mint(self):
|
||||
def test_mint_balance(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
@ -160,16 +142,19 @@ class TestBasic(TestDemurrageDefault):
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.assertEqual(balance, 2000)
|
||||
|
||||
|
||||
self.backend.time_travel(self.start_time + 61)
|
||||
self.backend.time_travel(self.start_time + (60 * 43200))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance_of(r)
|
||||
@ -234,7 +219,7 @@ class TestBasic(TestDemurrageDefault):
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
self.rpc.do(o)
|
||||
|
||||
self.backend.time_travel(self.start_time + 61)
|
||||
self.backend.time_travel(self.start_time + (60 * 43200))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
o = c.to_base_amount(self.address, 1000, sender_address=self.accounts[0])
|
||||
|
@ -18,42 +18,34 @@ from chainlib.eth.block import (
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrage
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
TAX_LEVEL = 2
|
||||
#TAX_LEVEL = 2
|
||||
|
||||
class TestBurn(TestDemurrage):
|
||||
class TestBurn(TestDemurrageDefault):
|
||||
|
||||
def setUp(self):
|
||||
super(TestBurn, self).setUp()
|
||||
|
||||
|
||||
def deploy(self, tax_level=None):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
|
||||
if self.mode == None:
|
||||
self.mode = 'MultiNocap'
|
||||
logg.debug('executing test setup default mode {}'.format(self.mode))
|
||||
|
||||
if tax_level != None:
|
||||
self.deployer.settings.demurrage_level = tax_level * (10 ** 32)
|
||||
self.deployer.settings.sink_address = self.accounts[9]
|
||||
self.deployer.sink_address = self.accounts[9]
|
||||
super(TestBurn, self).deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
#
|
||||
# def publish(self, tax_level=None):
|
||||
# nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
# c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
#
|
||||
# if tax_level != None:
|
||||
# self.publisher.settings.demurrage_level = tax_level * (10 ** 32)
|
||||
# self.publisher.settings.sink_address = self.accounts[9]
|
||||
# self.publisher.sink_address = self.accounts[9]
|
||||
# super(TestBurn, self).publish(c)
|
||||
|
||||
|
||||
# Burn tokens and immediately check balances and supply
|
||||
def test_burn_basic(self):
|
||||
self.deploy()
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
@ -94,7 +86,6 @@ class TestBurn(TestDemurrage):
|
||||
|
||||
# burn tokens and check sink balance and supply after first redistribution period
|
||||
def test_burned_redistribution(self):
|
||||
self.deploy()
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
@ -102,8 +93,8 @@ class TestBurn(TestDemurrage):
|
||||
r = self.rpc.do(o)
|
||||
|
||||
(tx_hash, o) = c.burn(self.address, self.accounts[0], 500000000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
self.rpc.do(o)
|
||||
|
||||
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.sink_address, 500000000)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
@ -112,7 +103,7 @@ class TestBurn(TestDemurrage):
|
||||
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
bal = c.parse_balance(r)
|
||||
self.assertEqual(bal, 416873881) # 9 periods demurrage
|
||||
self.assert_within(bal, 490000000, 1) # 2% == 10000000
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
@ -128,7 +119,7 @@ class TestBurn(TestDemurrage):
|
||||
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
bal = c.parse_balance(r)
|
||||
self.assert_within_lower(bal, 500000000, 0.0025)
|
||||
self.assert_within(bal, 500000000, 1)
|
||||
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * 2))
|
||||
|
||||
@ -147,12 +138,11 @@ class TestBurn(TestDemurrage):
|
||||
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
bal = c.parse_balance(r)
|
||||
self.assert_within_lower(bal, 500000000, 0.0025)
|
||||
self.assert_within_lower(bal, 500000000, 1)
|
||||
|
||||
|
||||
# burn tokens and check sink and taxed balance and supply after first redistribution period
|
||||
def test_burned_other_redistribution(self):
|
||||
self.deploy()
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
@ -170,7 +160,8 @@ class TestBurn(TestDemurrage):
|
||||
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
bal = c.parse_balance(r)
|
||||
self.assertEqual(bal, 416873881) # 9 periods demurrage
|
||||
#self.assertEqual(bal, 416873881) # 9 periods demurrage
|
||||
self.assert_within(bal, 490000000, 1)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
@ -186,12 +177,12 @@ class TestBurn(TestDemurrage):
|
||||
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
bal = c.parse_balance(r)
|
||||
self.assertEqual(bal, 408536403) # 9 periods demurrage
|
||||
self.assert_within(bal, 490000000, 1)
|
||||
|
||||
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
sink_bal = c.parse_balance(r)
|
||||
self.assert_within_lower(sink_bal, 500000000 - 408536403, 0.09) # TODO is this ok variance, 1.0 is ppm?
|
||||
self.assert_within_lower(sink_bal, 10000000, 1) # TODO is this ok variance, 1.0 is ppm?
|
||||
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * 2))
|
||||
|
||||
@ -209,7 +200,7 @@ class TestBurn(TestDemurrage):
|
||||
o = c.balance(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
next_bal = c.parse_balance(r)
|
||||
self.assertEqual(next_bal, 333803985) # 9 periods demurrage
|
||||
self.assert_within(next_bal, 480200000, 0.01)
|
||||
|
||||
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
@ -221,7 +212,6 @@ class TestBurn(TestDemurrage):
|
||||
|
||||
# verify expected results of balance and supply after multiple redistribution periods
|
||||
def test_burn_accumulate(self):
|
||||
self.deploy(tax_level=2/1000)
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
@ -251,6 +241,10 @@ class TestBurn(TestDemurrage):
|
||||
bob_bal = c.parse_balance(r)
|
||||
prev_bob_bal = bob_bal
|
||||
|
||||
o = c.balance(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
logg.info('sink has balance {}'.format(c.parse_balance(r)))
|
||||
|
||||
iterations = 100
|
||||
|
||||
for i in range(1, iterations + 1):
|
||||
@ -307,11 +301,11 @@ class TestBurn(TestDemurrage):
|
||||
sum_supply = burner_bal + bob_bal
|
||||
logg.debug('balances sink {} bob {} total {} supply real {} original {}'.format(sink_bal, bob_bal, sum_supply, new_supply, self.default_supply))
|
||||
|
||||
self.assert_within_lower(sum_supply, new_supply, 0.00001)
|
||||
self.assert_within_greater(burner_bal, balance_share - total_burned, 0.1)
|
||||
self.assert_within_lower(sum_supply, new_supply, 1)
|
||||
self.assert_within_lower(burner_bal, balance_share - total_burned + bob_refund, 1)
|
||||
|
||||
bob_delta = self.default_supply * ((2 / 1000000) / 1000)
|
||||
self.assert_within_lower(bob_bal, balance_share - bob_delta, 0.1)
|
||||
self.assert_within_greater(bob_bal, balance_share - bob_delta - bob_refund, 1)
|
||||
|
||||
self.assertEqual(total_burned, iterations * burn_rate)
|
||||
|
||||
|
@ -18,7 +18,7 @@ from hexathon import (
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageCap
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -26,18 +26,27 @@ logg = logging.getLogger()
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestCap(TestDemurrageCap):
|
||||
class TestCap(TestDemurrageDefault):
|
||||
|
||||
def test_cap_set(self):
|
||||
def test_cap(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
o = c.supply_cap(self.address, sender_address=self.accounts[0])
|
||||
|
||||
o = c.total_supply(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
logg.debug('r {}'.format(r))
|
||||
|
||||
(tx_hash, o) = c.set_max_supply(self.address, self.accounts[0], self.default_supply_cap)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.max_supply(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
cap = c.parse_supply_cap(r)
|
||||
self.assertEqual(cap, self.default_supply_cap)
|
||||
|
||||
|
||||
def test_cap(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], self.default_supply_cap)
|
||||
@ -53,15 +62,5 @@ class TestCap(TestDemurrageCap):
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
def test_cap_first(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], self.default_supply_cap + 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -13,32 +13,13 @@ from erc20_demurrage_token import DemurrageToken
|
||||
from erc20_demurrage_token.demurrage import DemurrageCalculator
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrage
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logg = logging.getLogger()
|
||||
|
||||
|
||||
class TestDemurragePeriods(TestDemurrage):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDemurragePeriods, self).setUp()
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
self.mode = os.environ.get('ERC20_DEMURRAGE_TOKEN_TEST_MODE')
|
||||
if self.mode == None:
|
||||
self.mode = 'MultiNocap'
|
||||
logg.debug('executing test setup default mode {}'.format(self.mode))
|
||||
|
||||
self.deployer.settings.demurrage_level = (2 / 1000) * (10 ** 32)
|
||||
self.deployer.settings.sink_address = self.accounts[9]
|
||||
self.deployer.sink_address = self.accounts[9]
|
||||
self.deploy(c, self.mode)
|
||||
|
||||
logg.info('deployed with mode {}'.format(self.mode))
|
||||
|
||||
class TestDemurragePeriods(TestDemurrageDefault):
|
||||
|
||||
# verify that tax level calculation is in ppm as expected
|
||||
def test_ppm(self):
|
@ -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()
|
98
python/tests/test_expiry.py
Normal file
98
python/tests/test_expiry.py
Normal file
@ -0,0 +1,98 @@
|
||||
# standard imports
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestExpire(TestDemurrageDefault):
|
||||
|
||||
def test_expires(self):
|
||||
mint_amount = self.default_supply
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
for i in range(3):
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[i+1], mint_amount)
|
||||
r = self.rpc.do(o)
|
||||
|
||||
(tx_hash, o) = c.set_expire_period(self.address, self.accounts[0], 2)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.expires(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
expiry_time = c.parse_expires(r)
|
||||
|
||||
self.backend.time_travel(expiry_time + 60)
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
o = block_by_number(r)
|
||||
r = self.rpc.do(o)
|
||||
self.assertGreaterEqual(r['timestamp'], expiry_time)
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.sink_address, self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.transfer(self.address, self.sink_address, self.accounts[2], 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.transfer(self.address, self.sink_address, self.accounts[2], 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assert_within(balance, 0.9604 * mint_amount, 1)
|
||||
|
||||
o = c.total_supply(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
supply = c.parse_balance(r)
|
||||
|
||||
(tx_hash, o) = c.change_period(self.address, self.sink_address)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
|
||||
o = c.decay_by(self.address, supply, int((expiry_time - self.start_time) / 60), sender_address=self.sink_address)
|
||||
r = self.rpc.do(o)
|
||||
target_balance = c.parse_balance(r)
|
||||
|
||||
self.assert_within_lower(balance, supply - target_balance, 0.0001)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -18,7 +18,7 @@ from chainlib.eth.block import (
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -28,42 +28,38 @@ testdir = os.path.dirname(__file__)
|
||||
|
||||
class TestGrowth(TestDemurrageDefault):
|
||||
|
||||
def test_grow_by(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
growth_factor = (1000000 + self.tax_level) / 1000000
|
||||
v = 1000000000
|
||||
o = c.grow_by(self.address, v, 1, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
g = c.parse_grow_by(r)
|
||||
self.assertEqual(int(v * growth_factor), g)
|
||||
|
||||
period = 10
|
||||
growth_factor = (1 + (self.tax_level) / 1000000) ** period
|
||||
o = c.grow_by(self.address, v, period, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
g = c.parse_grow_by(r)
|
||||
self.assertEqual(int(v * growth_factor), g)
|
||||
|
||||
|
||||
def test_decay_by(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
growth_factor = (1000000 - self.tax_level) / 1000000
|
||||
v = 1000000000
|
||||
o = c.decay_by(self.address, v, 1, sender_address=self.accounts[0])
|
||||
o = c.decay_by(self.address, v, 20000, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
g = c.parse_decay_by(r)
|
||||
self.assertEqual(int(v * growth_factor), g)
|
||||
self.assertEqual(int(g), 990690498)
|
||||
|
||||
o = c.decay_by(self.address, v, 43200, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
g = c.parse_decay_by(r)
|
||||
self.assertEqual(int(g), 980000000)
|
||||
|
||||
period = 10
|
||||
growth_factor = (1 - (self.tax_level) / 1000000) ** period
|
||||
o = c.decay_by(self.address, v, period, sender_address=self.accounts[0])
|
||||
|
||||
def test_decay_steps(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
v = 1000000000
|
||||
o = c.decay_by(self.address, v, 43200, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
g = c.parse_decay_by(r)
|
||||
self.assertEqual(int(v * growth_factor), g)
|
||||
gr = c.parse_decay_by(r)
|
||||
|
||||
v = 1000000000
|
||||
for i in range(100):
|
||||
o = c.decay_by(self.address, v, 432, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
v = c.parse_decay_by(r)
|
||||
|
||||
self.assert_within_lower(int(v), int(gr), 0.1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
77
python/tests/test_mint.py
Normal file
77
python/tests/test_mint.py
Normal file
@ -0,0 +1,77 @@
|
||||
# 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
|
||||
from chainlib.eth.block import block_by_number
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
class TestAmounts(TestDemurrageDefault):
|
||||
|
||||
def test_mint(self):
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
o = block_by_number(r)
|
||||
r = self.rpc.do(o)
|
||||
tb = r['timestamp']
|
||||
self.backend.time_travel(self.start_time + 800)
|
||||
o = block_latest()
|
||||
r = self.rpc.do(o)
|
||||
o = block_by_number(r)
|
||||
r = self.rpc.do(o)
|
||||
ta = r['timestamp']
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1000)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
|
||||
def test_writer(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[1], self.accounts[1], 1000)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.add_writer(self.address, self.accounts[0], self.accounts[1])
|
||||
r = 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.mint_to(self.address, self.accounts[1], self.accounts[1], 1000)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -16,12 +16,16 @@ from chainlib.eth.contract import (
|
||||
ABIContractEncoder,
|
||||
ABIContractType,
|
||||
)
|
||||
from hexathon import same as hex_same
|
||||
from hexathon import strip_0x
|
||||
from dexif import from_fixed
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
from erc20_demurrage_token import DemurrageRedistribution
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -30,6 +34,55 @@ testdir = os.path.dirname(__file__)
|
||||
|
||||
class TestPeriod(TestDemurrageDefault):
|
||||
|
||||
def test_period_and_amount(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
for i in range(100):
|
||||
self.backend.time_travel(self.start_time + int((self.period_seconds / 100) * (i + 1)))
|
||||
(tx_hash, o) = c.apply_demurrage(self.address, self.accounts[0], 0)
|
||||
self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
for lg in r['logs']:
|
||||
if hex_same(lg['topics'][0], '1c9c74563c32efd114cb36fb5e432d9386c8254d08456614804a33a3088ab736'):
|
||||
self.assert_equal_decimals(0.98, from_fixed(strip_0x(lg['data'])), 2)
|
||||
|
||||
|
||||
def test_period_demurrage(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], 1024)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds + int(self.period_seconds / 2))
|
||||
(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)
|
||||
|
||||
for lg in r['logs']:
|
||||
if hex_same(lg['topics'][0], '1c9c74563c32efd114cb36fb5e432d9386c8254d08456614804a33a3088ab736'):
|
||||
self.assert_equal_decimals(0.9701, from_fixed(strip_0x(lg['data'])), 4)
|
||||
|
||||
o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
redistribution_data = c.parse_redistributions(r)
|
||||
redistribution = DemurrageRedistribution(redistribution_data)
|
||||
logg.debug('fixxx {}'.format(redistribution.demurrage))
|
||||
|
||||
|
||||
def test_period(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
@ -57,15 +110,6 @@ class TestPeriod(TestDemurrageDefault):
|
||||
period = c.parse_to_redistribution_period(r)
|
||||
self.assertEqual(2, period)
|
||||
|
||||
o = c.redistributions(self.address, 1, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
redistribution = c.parse_redistributions(r)
|
||||
|
||||
o = c.to_redistribution_period(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_period(r)
|
||||
self.assertEqual(2, period)
|
||||
|
||||
o = c.actual_period(self.address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_actual_period(r)
|
||||
@ -73,16 +117,9 @@ class TestPeriod(TestDemurrageDefault):
|
||||
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_item(r)
|
||||
|
||||
# allow test code float rounding error to billionth
|
||||
modifier = (1 - (self.tax_level / 1000000)) ** (self.period_seconds / 60)
|
||||
modifier *= 10 ** 9
|
||||
modifier = int(modifier) * (10 ** (28 - 9))
|
||||
|
||||
period /= (10 ** (28 - 9))
|
||||
period = int(period) * (10 ** (28 - 9))
|
||||
self.assertEqual(modifier, period)
|
||||
period = from_fixed(r)
|
||||
redistro = DemurrageRedistribution(redistribution)
|
||||
logg.debug('redistro {} {}'.format(redistro, period))
|
||||
|
||||
self.backend.time_travel(self.start_time + self.period_seconds * 2)
|
||||
|
||||
@ -99,7 +136,7 @@ class TestPeriod(TestDemurrageDefault):
|
||||
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
period = c.parse_to_redistribution_item(r)
|
||||
period = from_fixed(r)
|
||||
|
||||
# allow test code float rounding error to billionth
|
||||
modifier = (1 - (self.tax_level / 1000000)) ** ((self.period_seconds * 2) / 60)
|
||||
@ -135,7 +172,7 @@ class TestPeriod(TestDemurrageDefault):
|
||||
r = self.rpc.do(o)
|
||||
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)
|
||||
balance = c.parse_balance_of(r)
|
||||
self.assertGreater(balance, 0)
|
||||
@ -185,7 +222,7 @@ class TestPeriod(TestDemurrageDefault):
|
||||
r = self.rpc.do(o)
|
||||
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)
|
||||
balance = c.parse_balance_of(r)
|
||||
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()
|
@ -16,13 +16,15 @@ from chainlib.eth.address import to_checksum_address
|
||||
from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
same as hex_same,
|
||||
)
|
||||
from dexif import from_fixed
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logg = logging.getLogger()
|
||||
@ -42,8 +44,8 @@ class TestRedistribution(TestDemurrageDefault):
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], supply)
|
||||
self.rpc.do(o)
|
||||
|
||||
for i in range(1, 10):
|
||||
logg.debug('execute time travel to period {}'.format(i))
|
||||
for i in range(1, 100):
|
||||
logg.info('execute time travel to period {}'.format(i))
|
||||
self.backend.time_travel(self.start_time + (self.period_seconds * i))
|
||||
(tx_hash, o) = c.change_period(self.address, self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
@ -51,19 +53,24 @@ class TestRedistribution(TestDemurrageDefault):
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
redistribution_value = 0
|
||||
for lg in r['logs']:
|
||||
if hex_same(lg['topics'][0], '0x9a2a887706623ad3ff7fc85652deeceabe9fe1e00466c597972079ee91ea40d3'):
|
||||
redistribution_value = int(strip_0x(lg['data']), 16)
|
||||
|
||||
o = c.redistributions(self.address, i, sender_address=self.accounts[0])
|
||||
redistribution = self.rpc.do(o)
|
||||
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage = c.parse_to_redistribution_item(r)
|
||||
demurrage = from_fixed(r)
|
||||
|
||||
o = c.redistributions(self.address, i-1, sender_address=self.accounts[0])
|
||||
redistribution = self.rpc.do(o)
|
||||
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage_previous = c.parse_to_redistribution_item(r)
|
||||
demurrage_previous = from_fixed(r)
|
||||
|
||||
o = c.balance_of(self.address, self.sink_address, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
@ -73,9 +80,9 @@ class TestRedistribution(TestDemurrageDefault):
|
||||
r = self.rpc.do(o)
|
||||
balance_minter = c.parse_balance(r)
|
||||
|
||||
logg.debug('testing sink {} mint {} adds up to supply {} with demurrage between {} and {}'.format(balance_sink, balance_minter, supply, demurrage_previous, demurrage))
|
||||
logg.info('testing redistro {} sink {} mint {} adds up to {} supply {} with demurrage between {} and {}'.format(redistribution_value, balance_sink, balance_minter, balance_sink + balance_minter, supply, demurrage_previous, demurrage))
|
||||
|
||||
self.assert_within_lower(balance_minter + balance_sink, supply, 0.001)
|
||||
self.assert_within(balance_minter + balance_sink, supply, 10)
|
||||
|
||||
|
||||
def test_redistribution_catchup_periods(self):
|
||||
@ -137,7 +144,7 @@ class TestRedistribution(TestDemurrageDefault):
|
||||
|
||||
logg.debug('testing sink {} mint {} adds up to supply {} with demurrage between {} and {}'.format(balance_sink, balance_minter, real_supply, demurrage_previous, demurrage))
|
||||
|
||||
self.assert_within_lower(balance_minter + balance_sink, supply, 0.001)
|
||||
self.assert_within_lower(balance_minter + balance_sink, supply, 0.1)
|
||||
|
||||
|
||||
# def test_redistribution_boundaries(self):
|
||||
|
@ -16,12 +16,14 @@ from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
)
|
||||
from dexif import to_fixed
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
from erc20_demurrage_token import DemurrageRedistribution
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageUnit
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -29,7 +31,8 @@ logg = logging.getLogger()
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestRedistribution(TestDemurrageUnit):
|
||||
class TestRedistribution(TestDemurrageDefault):
|
||||
|
||||
|
||||
|
||||
# TODO: move to "pure" test file when getdistribution is implemented in all contracts
|
||||
@ -40,11 +43,12 @@ class TestRedistribution(TestDemurrageUnit):
|
||||
demurrage = (1 - (self.tax_level / 1000000)) * (10**28)
|
||||
supply = self.default_supply
|
||||
|
||||
o = c.get_distribution(self.address, supply, demurrage, sender_address=self.accounts[0])
|
||||
#o = c.get_distribution(self.address, supply, demurrage, sender_address=self.accounts[0])
|
||||
o = c.get_distribution(self.address, supply, to_fixed(self.tax_level / 1000000), sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
distribution = c.parse_get_distribution(r)
|
||||
expected_distribution = self.default_supply * (self.tax_level / 1000000)
|
||||
self.assert_within_lower(distribution, expected_distribution, 1000)
|
||||
self.assert_within(distribution, expected_distribution, 100)
|
||||
|
||||
|
||||
def test_distribution_from_redistribution(self):
|
||||
@ -53,18 +57,17 @@ class TestRedistribution(TestDemurrageUnit):
|
||||
|
||||
demurrage = (1 - (self.tax_level / 100000)) * (10**28)
|
||||
|
||||
logg.debug('demurrage {}'.format(demurrage))
|
||||
supply = self.default_supply
|
||||
|
||||
o = c.to_redistribution(self.address, 0, demurrage, supply, 2, sender_address=self.accounts[0])
|
||||
o = c.to_redistribution(self.address, 0, to_fixed(self.tax_level / 1000000), supply, 2, sender_address=self.accounts[0])
|
||||
redistribution = self.rpc.do(o)
|
||||
|
||||
o = c.get_distribution_from_redistribution(self.address, redistribution, self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
distribution = c.parse_get_distribution(r)
|
||||
expected_distribution = (self.default_supply * self.tax_level) / 100000
|
||||
expected_distribution = (self.default_supply * (self.tax_level / 1000000))
|
||||
logg.debug('distribution {} supply {}'.format(distribution, self.default_supply))
|
||||
self.assert_within_lower(distribution, expected_distribution, 1000)
|
||||
self.assert_within(distribution, expected_distribution, 1000)
|
||||
|
||||
|
||||
def test_single_step_basic(self):
|
||||
@ -124,7 +127,7 @@ class TestRedistribution(TestDemurrageUnit):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
mint_amount = 100000000
|
||||
mint_amount = self.default_supply
|
||||
half_mint_amount = int(mint_amount / 2)
|
||||
|
||||
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[1], mint_amount)
|
||||
@ -156,7 +159,7 @@ class TestRedistribution(TestDemurrageUnit):
|
||||
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
self.assertEqual(balance, expected_balance)
|
||||
self.assert_within(balance, expected_balance, 10)
|
||||
|
||||
half_demurrage_amount = int((self.tax_level / 1000000) * half_mint_amount)
|
||||
|
||||
@ -182,8 +185,10 @@ class TestRedistribution(TestDemurrageUnit):
|
||||
supply = c.parse_to_redistribution_item(r)
|
||||
o = c.to_redistribution_demurrage_modifier(self.address, redistribution, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
demurrage = c.parse_to_redistribution_item(r)
|
||||
logg.debug('\nrediistribution {}\ndemurrage {}\nsupply {}'.format(redistribution, demurrage, supply))
|
||||
#demurrage = c.parse_to_redistribution_item(r)
|
||||
#logg.debug('\nrediistribution {}\ndemurrage {}\nsupply {}'.format(redistribution, demurrage, supply))
|
||||
redistro_item = DemurrageRedistribution(redistribution)
|
||||
logg.debug('redistribution {}'.format(redistro_item))
|
||||
|
||||
expected_balance = int(supply * (self.tax_level / 1000000))
|
||||
expected_balance_tolerance = 1
|
||||
|
@ -1,70 +0,0 @@
|
||||
# standard imports
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.error import JSONRPCException
|
||||
import eth_tester
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class Test(TestDemurrageDefault):
|
||||
|
||||
def test_fractional_state(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
with self.assertRaises(JSONRPCException):
|
||||
o = c.remainder(self.address, 2, 1, sender_address=self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
with self.assertRaises(JSONRPCException):
|
||||
o = c.remainder(self.address, 0, 100001, sender_address=self.accounts[0])
|
||||
self.rpc.do(o)
|
||||
|
||||
o = c.remainder(self.address, 1, 2, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, 0);
|
||||
|
||||
whole = 5000001
|
||||
parts = 20000
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
o = c.remainder(self.address, parts, whole, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
parts = 30000
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
o = c.remainder(self.address, parts, whole, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
parts = 40001
|
||||
expect = whole - (math.floor(whole/parts) * parts)
|
||||
o = c.remainder(self.address, parts, whole, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
remainder = c.parse_remainder(r)
|
||||
self.assertEqual(remainder, expect)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
195
python/tests/test_seal.py
Normal file
195
python/tests/test_seal.py
Normal file
@ -0,0 +1,195 @@
|
||||
# standard imports
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
# external imports
|
||||
from chainlib.eth.constant import ZERO_ADDRESS
|
||||
from chainlib.eth.nonce import RPCNonceOracle
|
||||
from chainlib.eth.tx import receipt
|
||||
from chainlib.eth.block import (
|
||||
block_latest,
|
||||
block_by_number,
|
||||
)
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
from erc20_demurrage_token.seal import ContractState
|
||||
from erc20_demurrage_token.seal import CONTRACT_SEAL_STATE_MAX
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
|
||||
class TestSeal(TestDemurrageDefault):
|
||||
|
||||
def test_seal_dup(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.seal(self.address, self.accounts[0], 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.seal(self.address, self.accounts[0], 1)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
|
||||
def test_seal_all(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.seal(self.address, self.accounts[0], CONTRACT_SEAL_STATE_MAX)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
o = c.is_sealed(self.address, 0, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assertTrue(c.parse_is_sealed(r))
|
||||
|
||||
|
||||
def test_seal_minter(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.add_minter(self.address, self.accounts[0], 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.seal(self.address, self.accounts[0], ContractState.MINTER_STATE)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.add_minter(self.address, self.accounts[0], self.accounts[2])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
o = c.is_sealed(self.address, ContractState.MINTER_STATE, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assertTrue(c.parse_is_sealed(r))
|
||||
|
||||
|
||||
def test_seal_expiry(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.set_expire_period(self.address, self.accounts[0], 10)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.set_expire_period(self.address, self.accounts[0], 20)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.seal(self.address, self.accounts[0], ContractState.EXPIRY_STATE)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.set_expire_period(self.address, self.accounts[0], 21)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
o = c.is_sealed(self.address, ContractState.EXPIRY_STATE, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assertTrue(c.parse_is_sealed(r))
|
||||
|
||||
|
||||
def test_seal_set_sink_address(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.set_sink_address(self.address, self.accounts[0], self.accounts[3])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.set_sink_address(self.address, self.accounts[0], self.accounts[4])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.seal(self.address, self.accounts[0], ContractState.SINK_STATE)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.set_sink_address(self.address, self.accounts[0], self.accounts[5])
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
o = c.is_sealed(self.address, ContractState.SINK_STATE, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assertTrue(c.parse_is_sealed(r))
|
||||
|
||||
|
||||
def test_seal_cap(self):
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
|
||||
|
||||
(tx_hash, o) = c.set_max_supply(self.address, self.accounts[0], 100)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.set_max_supply(self.address, self.accounts[0], 200)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.seal(self.address, self.accounts[0], ContractState.CAP_STATE)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
(tx_hash, o) = c.set_max_supply(self.address, self.accounts[0], 300)
|
||||
r = self.rpc.do(o)
|
||||
o = receipt(tx_hash)
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 0)
|
||||
|
||||
o = c.is_sealed(self.address, ContractState.CAP_STATE, sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
self.assertTrue(c.parse_is_sealed(r))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
|
@ -13,12 +13,13 @@ from hexathon import (
|
||||
strip_0x,
|
||||
add_0x,
|
||||
)
|
||||
from dexif import to_fixed
|
||||
|
||||
# local imports
|
||||
from erc20_demurrage_token import DemurrageToken
|
||||
|
||||
# test imports
|
||||
from erc20_demurrage_token.unittest.base import TestDemurrageSingle
|
||||
from erc20_demurrage_token.unittest import TestDemurrageDefault
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logg = logging.getLogger()
|
||||
@ -26,11 +27,9 @@ logg = logging.getLogger()
|
||||
testdir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TestRedistributionSingle(TestDemurrageSingle):
|
||||
|
||||
class TestRedistributionSingle(TestDemurrageDefault):
|
||||
|
||||
def test_single_even_if_multiple(self):
|
||||
|
||||
mint_amount = 100000000
|
||||
|
||||
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
|
||||
@ -64,7 +63,7 @@ class TestRedistributionSingle(TestDemurrageSingle):
|
||||
r = self.rpc.do(o)
|
||||
self.assertEqual(r['status'], 1)
|
||||
|
||||
tax_modifier = (1 - (self.tax_level / 1000000)) ** 10
|
||||
tax_modifier = 0.98
|
||||
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
|
||||
r = self.rpc.do(o)
|
||||
balance = c.parse_balance(r)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,23 +1,24 @@
|
||||
pragma solidity >= 0.8.0;
|
||||
|
||||
// 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 << 36) - 1) << 140
|
||||
import "aux/ABDKMath64x64.sol";
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
contract DemurrageTokenSingleNocap {
|
||||
|
||||
struct redistributionItem {
|
||||
uint32 period;
|
||||
uint72 value;
|
||||
uint64 demurrage;
|
||||
}
|
||||
redistributionItem[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
|
||||
|
||||
// Account balances
|
||||
mapping (address => uint256) account;
|
||||
|
||||
// Cached demurrage amount, ppm with 38 digit resolution
|
||||
uint128 public demurrageAmount;
|
||||
//uint128 public demurrageAmount;
|
||||
int128 public demurrageAmount;
|
||||
|
||||
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
|
||||
uint256 public demurrageTimestamp;
|
||||
@ -34,7 +35,7 @@ contract DemurrageTokenSingleCap {
|
||||
string public symbol;
|
||||
|
||||
// Implements ERC20
|
||||
uint256 public decimals;
|
||||
uint256 public immutable decimals;
|
||||
|
||||
// Implements ERC20
|
||||
//uint256 public totalSupply;
|
||||
@ -51,13 +52,13 @@ contract DemurrageTokenSingleCap {
|
||||
|
||||
// 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
|
||||
//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;
|
||||
//uint256 constant growthResolutionFactor = 1000000000000;
|
||||
|
||||
// demurrage decimal width; 38 places
|
||||
uint256 public immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
||||
//uint256 public immutable resolutionFactor = nanoDivider * growthResolutionFactor;
|
||||
|
||||
// Timestamp of start of periods (time which contract constructor was called)
|
||||
uint256 public immutable periodStart;
|
||||
@ -66,7 +67,9 @@ contract DemurrageTokenSingleCap {
|
||||
uint256 public immutable periodDuration;
|
||||
|
||||
// Demurrage in ppm per minute
|
||||
uint256 public immutable taxLevel;
|
||||
//uint256 public immutable decayLevel;
|
||||
// 64x64
|
||||
int128 public immutable decayLevel;
|
||||
|
||||
// Addresses allowed to mint new tokens
|
||||
mapping (address => bool) minter;
|
||||
@ -77,6 +80,13 @@ contract DemurrageTokenSingleCap {
|
||||
// Address to send unallocated redistribution tokens
|
||||
address public sinkAddress;
|
||||
|
||||
// timestamp when token contract expires
|
||||
uint256 public expires;
|
||||
bool expired;
|
||||
|
||||
// supply xap
|
||||
uint256 public maxSupply;
|
||||
|
||||
// Implements ERC20
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||
|
||||
@ -87,7 +97,7 @@ contract DemurrageTokenSingleCap {
|
||||
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);
|
||||
event Decayed(uint256 indexed _period, uint256 indexed _periodCount, int128 indexed _oldAmount, int128 _newAmount);
|
||||
|
||||
// When a new period threshold has been crossed
|
||||
event Period(uint256 _period);
|
||||
@ -96,7 +106,8 @@ contract DemurrageTokenSingleCap {
|
||||
event Redistribution(address indexed _account, uint256 indexed _period, uint256 _value);
|
||||
|
||||
// Temporary event used in development, will be removed on prod
|
||||
event Debug(bytes32 _foo);
|
||||
//event Debug(bytes32 _foo);
|
||||
event Debug(int128 indexed _foo, uint256 indexed _bar);
|
||||
|
||||
// Emitted when tokens are burned
|
||||
event Burn(address indexed _burner, uint256 _value);
|
||||
@ -104,10 +115,28 @@ contract DemurrageTokenSingleCap {
|
||||
// EIP173
|
||||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
|
||||
|
||||
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint128 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress) public {
|
||||
event Expired(uint256 _timestamp);
|
||||
|
||||
event Cap(uint256 indexed _oldCap, uint256 _newCap);
|
||||
|
||||
// Implements Sealer
|
||||
uint256 public sealState;
|
||||
uint8 constant WRITER_STATE = 1;
|
||||
uint8 constant SINK_STATE = 2;
|
||||
uint8 constant EXPIRY_STATE = 4;
|
||||
uint8 constant CAP_STATE = 8;
|
||||
uint256 constant public maxSealState = 15;
|
||||
event SealStateChange(bool indexed _final, uint256 _sealState);
|
||||
|
||||
|
||||
constructor(string memory _name, string memory _symbol, uint8 _decimals, int128 _decayLevel, uint256 _periodMinutes, address _defaultSinkAddress) {
|
||||
require(_decayLevel < (1 << 64));
|
||||
redistributionItem memory initialRedistribution;
|
||||
|
||||
//require(ABDKMath64x64.toUInt(_decayLevel) == 0);
|
||||
|
||||
// ACL setup
|
||||
owner = msg.sender;
|
||||
minter[owner] = true;
|
||||
|
||||
// ERC20 setup
|
||||
name = _name;
|
||||
@ -118,31 +147,91 @@ contract DemurrageTokenSingleCap {
|
||||
demurrageTimestamp = block.timestamp;
|
||||
periodStart = demurrageTimestamp;
|
||||
periodDuration = _periodMinutes * 60;
|
||||
demurrageAmount = uint128(nanoDivider) * 100;
|
||||
taxLevel = _taxLevelMinute; // Represents 38 decimal places
|
||||
bytes32 initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
|
||||
demurrageAmount = ABDKMath64x64.fromUInt(1);
|
||||
|
||||
decayLevel = ABDKMath64x64.ln(_decayLevel);
|
||||
initialRedistribution = toRedistribution(0, demurrageAmount, 0, 1);
|
||||
redistributions.push(initialRedistribution);
|
||||
|
||||
// Misc settings
|
||||
sinkAddress = _defaultSinkAddress;
|
||||
}
|
||||
|
||||
// Implements Sealer
|
||||
function seal(uint256 _state) public returns(uint256) {
|
||||
require(_state < 16, 'ERR_INVALID_STATE');
|
||||
require(_state & sealState == 0, 'ERR_ALREADY_LOCKED');
|
||||
sealState |= _state;
|
||||
emit SealStateChange(sealState == maxSealState, sealState);
|
||||
return uint256(sealState);
|
||||
}
|
||||
|
||||
// Implements Sealer
|
||||
function isSealed(uint256 _state) public view returns(bool) {
|
||||
require(_state < maxSealState);
|
||||
if (_state == 0) {
|
||||
return sealState == maxSealState;
|
||||
}
|
||||
return _state & sealState == _state;
|
||||
}
|
||||
|
||||
function setExpirePeriod(uint256 _expirePeriod) public {
|
||||
uint256 r;
|
||||
|
||||
require(!isSealed(EXPIRY_STATE));
|
||||
require(!expired);
|
||||
require(msg.sender == owner);
|
||||
r = periodStart + (_expirePeriod * periodDuration);
|
||||
require(r > expires);
|
||||
expires = r;
|
||||
}
|
||||
|
||||
function setMaxSupply(uint256 _cap) public {
|
||||
require(!isSealed(CAP_STATE));
|
||||
require(msg.sender == owner);
|
||||
require(_cap > totalSupply());
|
||||
emit Cap(maxSupply, _cap);
|
||||
maxSupply = _cap;
|
||||
}
|
||||
|
||||
// Change sink address for redistribution
|
||||
function setSinkAddress(address _sinkAddress) public {
|
||||
require(!isSealed(SINK_STATE));
|
||||
require(msg.sender == owner);
|
||||
sinkAddress = _sinkAddress;
|
||||
}
|
||||
|
||||
// Expire the contract if expire is set and we have gone over the threshold.
|
||||
// Finalizes demurrage up to the timestamp of the expiry.
|
||||
// The first approve, transfer or transferFrom call that hits the ex == 2 will get the tx mined. but without the actual effect. Otherwise we would have to wait until an external egent called applyExpiry to get the correct final balance.
|
||||
function applyExpiry() public returns(uint8) {
|
||||
if (expired) {
|
||||
return 1;
|
||||
}
|
||||
if (expires == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (block.timestamp >= expires) {
|
||||
applyDemurrageLimited(expires - demurrageTimestamp / 60);
|
||||
expired = true;
|
||||
emit Expired(block.timestamp);
|
||||
changePeriod();
|
||||
return 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Given address will be allowed to call the mintTo() function
|
||||
function addMinter(address _minter) public returns (bool) {
|
||||
function addWriter(address _minter) public returns (bool) {
|
||||
require(!isSealed(WRITER_STATE));
|
||||
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) {
|
||||
function deleteWriter(address _minter) public returns (bool) {
|
||||
require(!isSealed(WRITER_STATE));
|
||||
require(msg.sender == owner || _minter == msg.sender);
|
||||
minter[_minter] = false;
|
||||
return true;
|
||||
@ -150,20 +239,19 @@ contract DemurrageTokenSingleCap {
|
||||
|
||||
/// Implements ERC20
|
||||
function balanceOf(address _account) public view returns (uint256) {
|
||||
uint256 baseBalance;
|
||||
uint256 currentDemurragedAmount;
|
||||
int128 baseBalance;
|
||||
int128 currentDemurragedAmount;
|
||||
uint256 periodCount;
|
||||
|
||||
baseBalance = baseBalanceOf(_account);
|
||||
baseBalance = ABDKMath64x64.fromUInt(baseBalanceOf(_account));
|
||||
|
||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||
|
||||
currentDemurragedAmount = uint128(decayBy(demurrageAmount * 10000000000, periodCount));
|
||||
|
||||
return (baseBalance * currentDemurragedAmount) / (nanoDivider * 1000000000000);
|
||||
currentDemurragedAmount = ABDKMath64x64.mul(baseBalance, demurrageAmount);
|
||||
return decayBy(ABDKMath64x64.toUInt(currentDemurragedAmount), periodCount);
|
||||
}
|
||||
|
||||
/// Balance unmodified by demurrage
|
||||
// Balance unmodified by demurrage
|
||||
function baseBalanceOf(address _account) public view returns (uint256) {
|
||||
return account[_account];
|
||||
}
|
||||
@ -171,7 +259,6 @@ contract DemurrageTokenSingleCap {
|
||||
/// 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]);
|
||||
@ -188,7 +275,6 @@ contract DemurrageTokenSingleCap {
|
||||
/// 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]);
|
||||
@ -203,16 +289,31 @@ contract DemurrageTokenSingleCap {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send full balance of one account to another
|
||||
function sweep(address _account) public returns (uint256) {
|
||||
uint256 v;
|
||||
|
||||
v = account[msg.sender];
|
||||
account[msg.sender] = 0;
|
||||
account[_account] += v;
|
||||
return v;
|
||||
}
|
||||
|
||||
// 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(applyExpiry() == 0);
|
||||
require(minter[msg.sender] || msg.sender == owner, 'ERR_ACCESS');
|
||||
|
||||
changePeriod();
|
||||
baseAmount = toBaseAmount(_amount);
|
||||
if (maxSupply > 0) {
|
||||
require(supply + _amount <= maxSupply);
|
||||
}
|
||||
supply += _amount;
|
||||
|
||||
baseAmount = toBaseAmount(_amount);
|
||||
increaseBaseBalance(_beneficiary, baseAmount);
|
||||
emit Mint(msg.sender, _beneficiary, _amount);
|
||||
saveRedistributionSupply();
|
||||
@ -220,29 +321,36 @@ contract DemurrageTokenSingleCap {
|
||||
}
|
||||
|
||||
// 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;
|
||||
function toRedistribution(uint256 _participants, int128 _demurrageModifier, uint256 _value, uint256 _period) public pure returns(redistributionItem memory) {
|
||||
redistributionItem memory redistribution;
|
||||
|
||||
redistribution |= bytes32((_demurrageModifierPpm << shiftRedistributionDemurrage) & maskRedistributionDemurrage);
|
||||
redistribution |= bytes32((_value << shiftRedistributionValue) & maskRedistributionValue);
|
||||
redistribution |= bytes32(_period & maskRedistributionPeriod);
|
||||
_participants;
|
||||
redistribution.period = uint32(_period);
|
||||
redistribution.value = uint72(_value);
|
||||
redistribution.demurrage = uint64(uint128(_demurrageModifier) & 0xffffffffffffffff);
|
||||
return redistribution;
|
||||
|
||||
}
|
||||
|
||||
// Serializes the demurrage period part of the redistribution word
|
||||
function toRedistributionPeriod(bytes32 redistribution) public pure returns (uint256) {
|
||||
return uint256(redistribution) & maskRedistributionPeriod;
|
||||
function toRedistributionPeriod(redistributionItem memory _redistribution) public pure returns (uint256) {
|
||||
return uint256(_redistribution.period);
|
||||
}
|
||||
|
||||
// Serializes the supply part of the redistribution word
|
||||
function toRedistributionSupply(bytes32 redistribution) public pure returns (uint256) {
|
||||
return (uint256(redistribution) & maskRedistributionValue) >> shiftRedistributionValue;
|
||||
function toRedistributionSupply(redistributionItem memory _redistribution) public pure returns (uint256) {
|
||||
return uint256(_redistribution.value);
|
||||
}
|
||||
|
||||
// Serializes the number of participants part of the redistribution word
|
||||
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
|
||||
return (uint256(redistribution) & maskRedistributionDemurrage) >> shiftRedistributionDemurrage;
|
||||
function toRedistributionDemurrageModifier(redistributionItem memory _redistribution) public pure returns (int128) {
|
||||
int128 r;
|
||||
|
||||
r = int128(int64(_redistribution.demurrage) & int128(0x0000000000000000ffffffffffffffff));
|
||||
if (r == 0) {
|
||||
r = ABDKMath64x64.fromUInt(1);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Client accessor to the redistributions array length
|
||||
@ -252,15 +360,14 @@ contract DemurrageTokenSingleCap {
|
||||
|
||||
// Save the current total supply amount to the current redistribution period
|
||||
function saveRedistributionSupply() private returns (bool) {
|
||||
uint256 currentRedistribution;
|
||||
redistributionItem memory currentRedistribution;
|
||||
uint256 grownSupply;
|
||||
|
||||
grownSupply = totalSupply();
|
||||
currentRedistribution = uint256(redistributions[redistributions.length-1]);
|
||||
currentRedistribution &= (~maskRedistributionValue);
|
||||
currentRedistribution |= (grownSupply << shiftRedistributionValue);
|
||||
currentRedistribution = redistributions[redistributions.length-1];
|
||||
currentRedistribution.value = uint72(grownSupply);
|
||||
|
||||
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
|
||||
redistributions[redistributions.length-1] = currentRedistribution;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -270,28 +377,30 @@ contract DemurrageTokenSingleCap {
|
||||
}
|
||||
|
||||
// Retrieve next redistribution if the period threshold has been crossed
|
||||
function checkPeriod() private view returns (bytes32) {
|
||||
bytes32 lastRedistribution;
|
||||
function checkPeriod() private view returns (redistributionItem memory) {
|
||||
redistributionItem memory lastRedistribution;
|
||||
redistributionItem memory emptyRedistribution;
|
||||
uint256 currentPeriod;
|
||||
|
||||
lastRedistribution = redistributions[lastPeriod];
|
||||
currentPeriod = this.actualPeriod();
|
||||
if (currentPeriod <= toRedistributionPeriod(lastRedistribution)) {
|
||||
return bytes32(0x00);
|
||||
return emptyRedistribution;
|
||||
}
|
||||
return lastRedistribution;
|
||||
}
|
||||
|
||||
function getDistribution(uint256 _supply, uint256 _demurrageAmount) public view returns (uint256) {
|
||||
uint256 difference;
|
||||
function getDistribution(uint256 _supply, int128 _demurrageAmount) public pure returns (uint256) {
|
||||
int128 difference;
|
||||
|
||||
difference = _supply * (resolutionFactor - (_demurrageAmount * 10000000000));
|
||||
return difference / resolutionFactor;
|
||||
difference = ABDKMath64x64.mul(ABDKMath64x64.fromUInt(_supply), ABDKMath64x64.sub(ABDKMath64x64.fromUInt(1), _demurrageAmount));
|
||||
return _supply - ABDKMath64x64.toUInt(difference);
|
||||
|
||||
}
|
||||
|
||||
function getDistributionFromRedistribution(bytes32 _redistribution) public returns (uint256) {
|
||||
function getDistributionFromRedistribution(redistributionItem memory _redistribution) public pure returns (uint256) {
|
||||
uint256 redistributionSupply;
|
||||
uint256 redistributionDemurrage;
|
||||
int128 redistributionDemurrage;
|
||||
|
||||
redistributionSupply = toRedistributionSupply(_redistribution);
|
||||
redistributionDemurrage = toRedistributionDemurrageModifier(_redistribution);
|
||||
@ -299,35 +408,77 @@ contract DemurrageTokenSingleCap {
|
||||
}
|
||||
|
||||
// Returns the amount sent to the sink address
|
||||
function applyDefaultRedistribution(bytes32 _redistribution) private returns (uint256) {
|
||||
function applyDefaultRedistribution(redistributionItem memory _redistribution) private returns (uint256) {
|
||||
uint256 unit;
|
||||
uint256 baseUnit;
|
||||
|
||||
unit = getDistributionFromRedistribution(_redistribution);
|
||||
unit = totalSupply() - getDistributionFromRedistribution(_redistribution);
|
||||
baseUnit = toBaseAmount(unit) - totalSink;
|
||||
increaseBaseBalance(sinkAddress, baseUnit);
|
||||
emit Redistribution(sinkAddress, _redistribution.period, unit);
|
||||
lastPeriod += 1;
|
||||
totalSink += baseUnit;
|
||||
return unit;
|
||||
}
|
||||
|
||||
// Recalculate the demurrage modifier for the new period
|
||||
// Note that the supply for the consecutive period will be taken at the time of code execution, and thus not necessarily at the time when the redistribution period threshold was crossed.
|
||||
function changePeriod() public returns (bool) {
|
||||
redistributionItem memory currentRedistribution;
|
||||
redistributionItem memory nextRedistribution;
|
||||
redistributionItem memory lastRedistribution;
|
||||
uint256 currentPeriod;
|
||||
int128 lastDemurrageAmount;
|
||||
int128 nextRedistributionDemurrage;
|
||||
uint256 demurrageCounts;
|
||||
uint256 nextPeriod;
|
||||
|
||||
applyDemurrage();
|
||||
currentRedistribution = checkPeriod();
|
||||
if (isEmptyRedistribution(currentRedistribution)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate the decay from previous redistributino
|
||||
lastRedistribution = redistributions[lastPeriod];
|
||||
currentPeriod = toRedistributionPeriod(currentRedistribution);
|
||||
nextPeriod = currentPeriod + 1;
|
||||
lastDemurrageAmount = toRedistributionDemurrageModifier(lastRedistribution);
|
||||
demurrageCounts = (periodDuration * currentPeriod) / 60;
|
||||
// TODO refactor decayby to take int128 then DRY with it
|
||||
nextRedistributionDemurrage = ABDKMath64x64.exp(ABDKMath64x64.mul(decayLevel, ABDKMath64x64.fromUInt(demurrageCounts)));
|
||||
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply(), nextPeriod);
|
||||
redistributions.push(nextRedistribution);
|
||||
|
||||
applyDefaultRedistribution(nextRedistribution);
|
||||
emit Period(nextPeriod);
|
||||
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) {
|
||||
function applyDemurrage() public returns (uint256) {
|
||||
return applyDemurrageLimited(0);
|
||||
}
|
||||
|
||||
function applyDemurrageLimited(uint256 _rounds) public returns (bool) {
|
||||
// returns true if expired
|
||||
function applyDemurrageLimited(uint256 _rounds) public returns (uint256) {
|
||||
int128 v;
|
||||
uint256 periodCount;
|
||||
uint256 lastDemurrageAmount;
|
||||
int128 periodPoint;
|
||||
int128 lastDemurrageAmount;
|
||||
|
||||
if (expired) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
periodCount = getMinutesDelta(demurrageTimestamp);
|
||||
if (periodCount == 0) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
lastDemurrageAmount = demurrageAmount;
|
||||
|
||||
@ -337,11 +488,14 @@ contract DemurrageTokenSingleCap {
|
||||
periodCount = _rounds;
|
||||
}
|
||||
|
||||
demurrageAmount = uint128(decayBy(lastDemurrageAmount, periodCount));
|
||||
//demurragePeriod = epochPeriodCount;
|
||||
periodPoint = ABDKMath64x64.fromUInt(periodCount);
|
||||
v = ABDKMath64x64.mul(decayLevel, periodPoint);
|
||||
v = ABDKMath64x64.exp(v);
|
||||
demurrageAmount = ABDKMath64x64.mul(demurrageAmount, v);
|
||||
|
||||
demurrageTimestamp = demurrageTimestamp + (periodCount * 60);
|
||||
emit Decayed(demurrageTimestamp, periodCount, lastDemurrageAmount, demurrageAmount);
|
||||
return true;
|
||||
return periodCount;
|
||||
}
|
||||
|
||||
// Return timestamp of start of period threshold
|
||||
@ -354,77 +508,54 @@ contract DemurrageTokenSingleCap {
|
||||
return (block.timestamp - _target) / 60;
|
||||
}
|
||||
|
||||
// Recalculate the demurrage modifier for the new period
|
||||
// Note that the supply for the consecutive period will be taken at the time of code execution, and thus not necessarily at the time when the redistribution period threshold was crossed.
|
||||
function changePeriod() public returns (bool) {
|
||||
bytes32 currentRedistribution;
|
||||
bytes32 nextRedistribution;
|
||||
uint256 currentPeriod;
|
||||
uint256 lastDemurrageAmount;
|
||||
bytes32 lastRedistribution;
|
||||
uint256 nextRedistributionDemurrage;
|
||||
uint256 demurrageCounts;
|
||||
uint256 nextPeriod;
|
||||
|
||||
applyDemurrage();
|
||||
currentRedistribution = checkPeriod();
|
||||
if (currentRedistribution == bytes32(0x00)) {
|
||||
function isEmptyRedistribution(redistributionItem memory _redistribution) public pure returns(bool) {
|
||||
if (_redistribution.period > 0) {
|
||||
return false;
|
||||
}
|
||||
if (_redistribution.value > 0) {
|
||||
return false;
|
||||
}
|
||||
if (_redistribution.demurrage > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate the decay from previous redistributino
|
||||
lastRedistribution = redistributions[lastPeriod];
|
||||
currentPeriod = toRedistributionPeriod(currentRedistribution);
|
||||
nextPeriod = currentPeriod + 1;
|
||||
lastDemurrageAmount = toRedistributionDemurrageModifier(lastRedistribution);
|
||||
demurrageCounts = periodDuration / 60;
|
||||
nextRedistributionDemurrage = decayBy(lastDemurrageAmount, demurrageCounts);
|
||||
|
||||
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;
|
||||
function decayBy(uint256 _value, uint256 _period) public view returns (uint256) {
|
||||
int128 valuePoint;
|
||||
int128 periodPoint;
|
||||
int128 v;
|
||||
|
||||
valuePoint = ABDKMath64x64.fromUInt(_value);
|
||||
periodPoint = ABDKMath64x64.fromUInt(_period);
|
||||
|
||||
for (uint256 i = 0; i < _period; i++) {
|
||||
valueFactor = valueFactor - ((valueFactor * truncatedTaxLevel) / growthResolutionFactor);
|
||||
}
|
||||
return (valueFactor * _value) / growthResolutionFactor;
|
||||
v = ABDKMath64x64.mul(decayLevel, periodPoint);
|
||||
v = ABDKMath64x64.exp(v);
|
||||
v = ABDKMath64x64.mul(valuePoint, v);
|
||||
return ABDKMath64x64.toUInt(v);
|
||||
}
|
||||
|
||||
|
||||
// Inflates the given amount according to the current demurrage modifier
|
||||
function toBaseAmount(uint256 _value) public view returns (uint256) {
|
||||
return (_value * resolutionFactor) / (demurrageAmount * 10000000000);
|
||||
int128 r;
|
||||
r = ABDKMath64x64.div(ABDKMath64x64.fromUInt(_value), demurrageAmount);
|
||||
return ABDKMath64x64.toUInt(r);
|
||||
}
|
||||
|
||||
// Implements ERC20, triggers tax and/or redistribution
|
||||
function approve(address _spender, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
uint8 ex;
|
||||
|
||||
ex = applyExpiry();
|
||||
if (ex == 2) {
|
||||
return false;
|
||||
} else if (ex > 0) {
|
||||
revert('EXPIRED');
|
||||
}
|
||||
if (allowance[msg.sender][_spender] > 0) {
|
||||
require(_value == 0, 'ZERO_FIRST');
|
||||
}
|
||||
@ -468,7 +599,14 @@ contract DemurrageTokenSingleCap {
|
||||
function transfer(address _to, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
bool result;
|
||||
uint8 ex;
|
||||
|
||||
ex = applyExpiry();
|
||||
if (ex == 2) {
|
||||
return false;
|
||||
} else if (ex > 0) {
|
||||
revert('EXPIRED');
|
||||
}
|
||||
changePeriod();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
@ -481,7 +619,14 @@ contract DemurrageTokenSingleCap {
|
||||
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
|
||||
uint256 baseValue;
|
||||
bool result;
|
||||
uint8 ex;
|
||||
|
||||
ex = applyExpiry();
|
||||
if (ex == 2) {
|
||||
return false;
|
||||
} else if (ex > 0) {
|
||||
revert('EXPIRED');
|
||||
}
|
||||
changePeriod();
|
||||
|
||||
baseValue = toBaseAmount(_value);
|
||||
@ -496,8 +641,6 @@ contract DemurrageTokenSingleCap {
|
||||
|
||||
// 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);
|
||||
|
||||
@ -508,6 +651,7 @@ contract DemurrageTokenSingleCap {
|
||||
function transferOwnership(address _newOwner) public returns (bool) {
|
||||
require(msg.sender == owner);
|
||||
newOwner = _newOwner;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements OwnedAccepter
|
||||
@ -519,19 +663,22 @@ contract DemurrageTokenSingleCap {
|
||||
owner = newOwner;
|
||||
newOwner = address(0);
|
||||
emit OwnershipTransferred(oldOwner, owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Explicitly and irretrievably burn tokens
|
||||
// Only token minters can burn tokens
|
||||
function burn(uint256 _value) public {
|
||||
require(minter[msg.sender]);
|
||||
function burn(uint256 _value) public returns (bool) {
|
||||
require(applyExpiry() == 0);
|
||||
require(minter[msg.sender] || msg.sender == owner, 'ERR_ACCESS');
|
||||
require(_value <= account[msg.sender]);
|
||||
uint256 _delta = toBaseAmount(_value);
|
||||
|
||||
applyDemurrage();
|
||||
//applyDemurrage();
|
||||
decreaseBaseBalance(msg.sender, _delta);
|
||||
burned += _value;
|
||||
emit Burn(msg.sender, _value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Implements ERC20
|
||||
@ -544,6 +691,12 @@ contract DemurrageTokenSingleCap {
|
||||
return burned;
|
||||
}
|
||||
|
||||
// Return total number of tokens ever minted
|
||||
function totalMinted() public view returns (uint256) {
|
||||
return supply;
|
||||
}
|
||||
|
||||
|
||||
// Implements EIP165
|
||||
function supportsInterface(bytes4 _sum) public pure returns (bool) {
|
||||
if (_sum == 0xc6bb4b70) { // ERC20
|
||||
@ -558,9 +711,6 @@ contract DemurrageTokenSingleCap {
|
||||
if (_sum == 0x9493f8b2) { // EIP173
|
||||
return true;
|
||||
}
|
||||
if (_sum == 0x37a47be4) { // OwnedAccepter
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,14 @@
|
||||
SOLC = /usr/bin/solc
|
||||
|
||||
all: multi single
|
||||
|
||||
multi_nocap:
|
||||
$(SOLC) DemurrageTokenMultiNocap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiNocap.json
|
||||
$(SOLC) DemurrageTokenMultiNocap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiNocap.bin
|
||||
truncate -s -1 DemurrageTokenMultiNocap.bin
|
||||
|
||||
multi_cap:
|
||||
$(SOLC) DemurrageTokenMultiCap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiCap.json
|
||||
$(SOLC) DemurrageTokenMultiCap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenMultiCap.bin
|
||||
truncate -s -1 DemurrageTokenMultiCap.bin
|
||||
|
||||
multi: multi_nocap multi_cap
|
||||
all: single_nocap
|
||||
|
||||
single_nocap:
|
||||
$(SOLC) DemurrageTokenSingleNocap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleNocap.json
|
||||
$(SOLC) DemurrageTokenSingleNocap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleNocap.bin
|
||||
$(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 --metadata --evm-version byzantium | awk 'NR==4' > DemurrageTokenSingleNocap.metadata.json
|
||||
truncate -s -1 DemurrageTokenSingleNocap.bin
|
||||
|
||||
single_cap:
|
||||
$(SOLC) DemurrageTokenSingleCap.sol --abi --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleCap.json
|
||||
$(SOLC) DemurrageTokenSingleCap.sol --bin --evm-version byzantium | awk 'NR>3' > DemurrageTokenSingleCap.bin
|
||||
truncate -s -1 DemurrageTokenSingleCap.bin
|
||||
|
||||
single: single_nocap single_cap
|
||||
single: single_nocap
|
||||
|
||||
test: all
|
||||
python ../python/tests/test_basic.py
|
||||
@ -33,10 +17,7 @@ test: all
|
||||
python ../python/tests/test_pure.py
|
||||
|
||||
install: all
|
||||
#cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
|
||||
cp -v DemurrageTokenSingleNocap.{json,bin} ../python/erc20_demurrage_token/data/
|
||||
|
||||
install-broken: all
|
||||
cp -v DemurrageToken*.{json,bin} ../python/erc20_demurrage_token/data/
|
||||
cp -v DemurrageToken*.json ../python/erc20_demurrage_token/data/
|
||||
cp -v DemurrageToken*.bin ../python/erc20_demurrage_token/data/
|
||||
|
||||
.PHONY: test install
|
||||
|
752
solidity/aux/ABDKMath64x64.sol
Normal file
752
solidity/aux/ABDKMath64x64.sol
Normal file
@ -0,0 +1,752 @@
|
||||
// SPDX-License-Identifier: BSD-4-Clause
|
||||
/*
|
||||
* ABDK Math 64.64 Smart Contract Library. Copyright © 2019 by ABDK Consulting.
|
||||
* Author: Mikhail Vladimirov <mikhail.vladimirov@gmail.com>
|
||||
*/
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* Smart contract library of mathematical functions operating with signed
|
||||
* 64.64-bit fixed point numbers. Signed 64.64-bit fixed point number is
|
||||
* basically a simple fraction whose numerator is signed 128-bit integer and
|
||||
* denominator is 2^64. As long as denominator is always the same, there is no
|
||||
* need to store it, thus in Solidity signed 64.64-bit fixed point numbers are
|
||||
* represented by int128 type holding only the numerator.
|
||||
*/
|
||||
library ABDKMath64x64 {
|
||||
/*
|
||||
* Minimum value signed 64.64-bit fixed point number may have.
|
||||
*/
|
||||
int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;
|
||||
|
||||
/*
|
||||
* Maximum value signed 64.64-bit fixed point number may have.
|
||||
*/
|
||||
int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
|
||||
|
||||
/**
|
||||
* Convert signed 256-bit integer number into signed 64.64-bit fixed point
|
||||
* number. Revert on overflow.
|
||||
*
|
||||
* @param x signed 256-bit integer number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function fromInt (int256 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);
|
||||
return int128 (x << 64);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert signed 64.64 fixed point number into signed 64-bit integer number
|
||||
* rounding down.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64-bit integer number
|
||||
*/
|
||||
function toInt (int128 x) internal pure returns (int64) {
|
||||
unchecked {
|
||||
return int64 (x >> 64);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert unsigned 256-bit integer number into signed 64.64-bit fixed point
|
||||
* number. Revert on overflow.
|
||||
*
|
||||
* @param x unsigned 256-bit integer number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function fromUInt (uint256 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x <= 0x7FFFFFFFFFFFFFFF);
|
||||
return int128 (int256 (x << 64));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert signed 64.64 fixed point number into unsigned 64-bit integer
|
||||
* number rounding down. Revert on underflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return unsigned 64-bit integer number
|
||||
*/
|
||||
function toUInt (int128 x) internal pure returns (uint64) {
|
||||
unchecked {
|
||||
require (x >= 0);
|
||||
return uint64 (uint128 (x >> 64));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert signed 128.128 fixed point number into signed 64.64-bit fixed point
|
||||
* number rounding down. Revert on overflow.
|
||||
*
|
||||
* @param x signed 128.128-bin fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function from128x128 (int256 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
int256 result = x >> 64;
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert signed 64.64 fixed point number into signed 128.128 fixed point
|
||||
* number.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 128.128 fixed point number
|
||||
*/
|
||||
function to128x128 (int128 x) internal pure returns (int256) {
|
||||
unchecked {
|
||||
return int256 (x) << 64;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x + y. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function add (int128 x, int128 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
int256 result = int256(x) + y;
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x - y. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function sub (int128 x, int128 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
int256 result = int256(x) - y;
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x * y rounding down. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function mul (int128 x, int128 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
int256 result = int256(x) * y >> 64;
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x * y rounding towards zero, where x is signed 64.64 fixed point
|
||||
* number and y is signed 256-bit integer number. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64 fixed point number
|
||||
* @param y signed 256-bit integer number
|
||||
* @return signed 256-bit integer number
|
||||
*/
|
||||
function muli (int128 x, int256 y) internal pure returns (int256) {
|
||||
unchecked {
|
||||
if (x == MIN_64x64) {
|
||||
require (y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF &&
|
||||
y <= 0x1000000000000000000000000000000000000000000000000);
|
||||
return -y << 63;
|
||||
} else {
|
||||
bool negativeResult = false;
|
||||
if (x < 0) {
|
||||
x = -x;
|
||||
negativeResult = true;
|
||||
}
|
||||
if (y < 0) {
|
||||
y = -y; // We rely on overflow behavior here
|
||||
negativeResult = !negativeResult;
|
||||
}
|
||||
uint256 absoluteResult = mulu (x, uint256 (y));
|
||||
if (negativeResult) {
|
||||
require (absoluteResult <=
|
||||
0x8000000000000000000000000000000000000000000000000000000000000000);
|
||||
return -int256 (absoluteResult); // We rely on overflow behavior here
|
||||
} else {
|
||||
require (absoluteResult <=
|
||||
0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
return int256 (absoluteResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x * y rounding down, where x is signed 64.64 fixed point number
|
||||
* and y is unsigned 256-bit integer number. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64 fixed point number
|
||||
* @param y unsigned 256-bit integer number
|
||||
* @return unsigned 256-bit integer number
|
||||
*/
|
||||
function mulu (int128 x, uint256 y) internal pure returns (uint256) {
|
||||
unchecked {
|
||||
if (y == 0) return 0;
|
||||
|
||||
require (x >= 0);
|
||||
|
||||
uint256 lo = (uint256 (int256 (x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;
|
||||
uint256 hi = uint256 (int256 (x)) * (y >> 128);
|
||||
|
||||
require (hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
hi <<= 64;
|
||||
|
||||
require (hi <=
|
||||
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);
|
||||
return hi + lo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x / y rounding towards zero. Revert on overflow or when y is
|
||||
* zero.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function div (int128 x, int128 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (y != 0);
|
||||
int256 result = (int256 (x) << 64) / y;
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x / y rounding towards zero, where x and y are signed 256-bit
|
||||
* integer numbers. Revert on overflow or when y is zero.
|
||||
*
|
||||
* @param x signed 256-bit integer number
|
||||
* @param y signed 256-bit integer number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function divi (int256 x, int256 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (y != 0);
|
||||
|
||||
bool negativeResult = false;
|
||||
if (x < 0) {
|
||||
x = -x; // We rely on overflow behavior here
|
||||
negativeResult = true;
|
||||
}
|
||||
if (y < 0) {
|
||||
y = -y; // We rely on overflow behavior here
|
||||
negativeResult = !negativeResult;
|
||||
}
|
||||
uint128 absoluteResult = divuu (uint256 (x), uint256 (y));
|
||||
if (negativeResult) {
|
||||
require (absoluteResult <= 0x80000000000000000000000000000000);
|
||||
return -int128 (absoluteResult); // We rely on overflow behavior here
|
||||
} else {
|
||||
require (absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
return int128 (absoluteResult); // We rely on overflow behavior here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
|
||||
* integer numbers. Revert on overflow or when y is zero.
|
||||
*
|
||||
* @param x unsigned 256-bit integer number
|
||||
* @param y unsigned 256-bit integer number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function divu (uint256 x, uint256 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (y != 0);
|
||||
uint128 result = divuu (x, y);
|
||||
require (result <= uint128 (MAX_64x64));
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate -x. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function neg (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x != MIN_64x64);
|
||||
return -x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate |x|. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function abs (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x != MIN_64x64);
|
||||
return x < 0 ? -x : x;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate 1 / x rounding towards zero. Revert on overflow or when x is
|
||||
* zero.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function inv (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x != 0);
|
||||
int256 result = int256 (0x100000000000000000000000000000000) / x;
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function avg (int128 x, int128 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
return int128 ((int256 (x) + int256 (y)) >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.
|
||||
* Revert on overflow or in case x * y is negative.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function gavg (int128 x, int128 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
int256 m = int256 (x) * int256 (y);
|
||||
require (m >= 0);
|
||||
require (m <
|
||||
0x4000000000000000000000000000000000000000000000000000000000000000);
|
||||
return int128 (sqrtu (uint256 (m)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number
|
||||
* and y is unsigned 256-bit integer number. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @param y uint256 value
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function pow (int128 x, uint256 y) internal pure returns (int128) {
|
||||
unchecked {
|
||||
bool negative = x < 0 && y & 1 == 1;
|
||||
|
||||
uint256 absX = uint128 (x < 0 ? -x : x);
|
||||
uint256 absResult;
|
||||
absResult = 0x100000000000000000000000000000000;
|
||||
|
||||
if (absX <= 0x10000000000000000) {
|
||||
absX <<= 63;
|
||||
while (y != 0) {
|
||||
if (y & 0x1 != 0) {
|
||||
absResult = absResult * absX >> 127;
|
||||
}
|
||||
absX = absX * absX >> 127;
|
||||
|
||||
if (y & 0x2 != 0) {
|
||||
absResult = absResult * absX >> 127;
|
||||
}
|
||||
absX = absX * absX >> 127;
|
||||
|
||||
if (y & 0x4 != 0) {
|
||||
absResult = absResult * absX >> 127;
|
||||
}
|
||||
absX = absX * absX >> 127;
|
||||
|
||||
if (y & 0x8 != 0) {
|
||||
absResult = absResult * absX >> 127;
|
||||
}
|
||||
absX = absX * absX >> 127;
|
||||
|
||||
y >>= 4;
|
||||
}
|
||||
|
||||
absResult >>= 64;
|
||||
} else {
|
||||
uint256 absXShift = 63;
|
||||
if (absX < 0x1000000000000000000000000) { absX <<= 32; absXShift -= 32; }
|
||||
if (absX < 0x10000000000000000000000000000) { absX <<= 16; absXShift -= 16; }
|
||||
if (absX < 0x1000000000000000000000000000000) { absX <<= 8; absXShift -= 8; }
|
||||
if (absX < 0x10000000000000000000000000000000) { absX <<= 4; absXShift -= 4; }
|
||||
if (absX < 0x40000000000000000000000000000000) { absX <<= 2; absXShift -= 2; }
|
||||
if (absX < 0x80000000000000000000000000000000) { absX <<= 1; absXShift -= 1; }
|
||||
|
||||
uint256 resultShift = 0;
|
||||
while (y != 0) {
|
||||
require (absXShift < 64);
|
||||
|
||||
if (y & 0x1 != 0) {
|
||||
absResult = absResult * absX >> 127;
|
||||
resultShift += absXShift;
|
||||
if (absResult > 0x100000000000000000000000000000000) {
|
||||
absResult >>= 1;
|
||||
resultShift += 1;
|
||||
}
|
||||
}
|
||||
absX = absX * absX >> 127;
|
||||
absXShift <<= 1;
|
||||
if (absX >= 0x100000000000000000000000000000000) {
|
||||
absX >>= 1;
|
||||
absXShift += 1;
|
||||
}
|
||||
|
||||
y >>= 1;
|
||||
}
|
||||
|
||||
require (resultShift < 64);
|
||||
absResult >>= 64 - resultShift;
|
||||
}
|
||||
int256 result = negative ? -int256 (absResult) : int256 (absResult);
|
||||
require (result >= MIN_64x64 && result <= MAX_64x64);
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate sqrt (x) rounding down. Revert if x < 0.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function sqrt (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x >= 0);
|
||||
return int128 (sqrtu (uint256 (int256 (x)) << 64));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate binary logarithm of x. Revert if x <= 0.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function log_2 (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x > 0);
|
||||
|
||||
int256 msb = 0;
|
||||
int256 xc = x;
|
||||
if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }
|
||||
if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
|
||||
if (xc >= 0x10000) { xc >>= 16; msb += 16; }
|
||||
if (xc >= 0x100) { xc >>= 8; msb += 8; }
|
||||
if (xc >= 0x10) { xc >>= 4; msb += 4; }
|
||||
if (xc >= 0x4) { xc >>= 2; msb += 2; }
|
||||
if (xc >= 0x2) msb += 1; // No need to shift xc anymore
|
||||
|
||||
int256 result = msb - 64 << 64;
|
||||
uint256 ux = uint256 (int256 (x)) << uint256 (127 - msb);
|
||||
for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {
|
||||
ux *= ux;
|
||||
uint256 b = ux >> 255;
|
||||
ux >>= 127 + b;
|
||||
result += bit * int256 (b);
|
||||
}
|
||||
|
||||
return int128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate natural logarithm of x. Revert if x <= 0.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function ln (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x > 0);
|
||||
|
||||
return int128 (int256 (
|
||||
uint256 (int256 (log_2 (x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate binary exponent of x. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function exp_2 (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x < 0x400000000000000000); // Overflow
|
||||
|
||||
if (x < -0x400000000000000000) return 0; // Underflow
|
||||
|
||||
uint256 result = 0x80000000000000000000000000000000;
|
||||
|
||||
if (x & 0x8000000000000000 > 0)
|
||||
result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;
|
||||
if (x & 0x4000000000000000 > 0)
|
||||
result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;
|
||||
if (x & 0x2000000000000000 > 0)
|
||||
result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;
|
||||
if (x & 0x1000000000000000 > 0)
|
||||
result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;
|
||||
if (x & 0x800000000000000 > 0)
|
||||
result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;
|
||||
if (x & 0x400000000000000 > 0)
|
||||
result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;
|
||||
if (x & 0x200000000000000 > 0)
|
||||
result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;
|
||||
if (x & 0x100000000000000 > 0)
|
||||
result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;
|
||||
if (x & 0x80000000000000 > 0)
|
||||
result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;
|
||||
if (x & 0x40000000000000 > 0)
|
||||
result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;
|
||||
if (x & 0x20000000000000 > 0)
|
||||
result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;
|
||||
if (x & 0x10000000000000 > 0)
|
||||
result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;
|
||||
if (x & 0x8000000000000 > 0)
|
||||
result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;
|
||||
if (x & 0x4000000000000 > 0)
|
||||
result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;
|
||||
if (x & 0x2000000000000 > 0)
|
||||
result = result * 0x1000162E525EE054754457D5995292026 >> 128;
|
||||
if (x & 0x1000000000000 > 0)
|
||||
result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;
|
||||
if (x & 0x800000000000 > 0)
|
||||
result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;
|
||||
if (x & 0x400000000000 > 0)
|
||||
result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;
|
||||
if (x & 0x200000000000 > 0)
|
||||
result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;
|
||||
if (x & 0x100000000000 > 0)
|
||||
result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;
|
||||
if (x & 0x80000000000 > 0)
|
||||
result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;
|
||||
if (x & 0x40000000000 > 0)
|
||||
result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;
|
||||
if (x & 0x20000000000 > 0)
|
||||
result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;
|
||||
if (x & 0x10000000000 > 0)
|
||||
result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;
|
||||
if (x & 0x8000000000 > 0)
|
||||
result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;
|
||||
if (x & 0x4000000000 > 0)
|
||||
result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;
|
||||
if (x & 0x2000000000 > 0)
|
||||
result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;
|
||||
if (x & 0x1000000000 > 0)
|
||||
result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;
|
||||
if (x & 0x800000000 > 0)
|
||||
result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;
|
||||
if (x & 0x400000000 > 0)
|
||||
result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;
|
||||
if (x & 0x200000000 > 0)
|
||||
result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;
|
||||
if (x & 0x100000000 > 0)
|
||||
result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;
|
||||
if (x & 0x80000000 > 0)
|
||||
result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;
|
||||
if (x & 0x40000000 > 0)
|
||||
result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;
|
||||
if (x & 0x20000000 > 0)
|
||||
result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;
|
||||
if (x & 0x10000000 > 0)
|
||||
result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;
|
||||
if (x & 0x8000000 > 0)
|
||||
result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;
|
||||
if (x & 0x4000000 > 0)
|
||||
result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;
|
||||
if (x & 0x2000000 > 0)
|
||||
result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;
|
||||
if (x & 0x1000000 > 0)
|
||||
result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;
|
||||
if (x & 0x800000 > 0)
|
||||
result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;
|
||||
if (x & 0x400000 > 0)
|
||||
result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;
|
||||
if (x & 0x200000 > 0)
|
||||
result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;
|
||||
if (x & 0x100000 > 0)
|
||||
result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;
|
||||
if (x & 0x80000 > 0)
|
||||
result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;
|
||||
if (x & 0x40000 > 0)
|
||||
result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;
|
||||
if (x & 0x20000 > 0)
|
||||
result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;
|
||||
if (x & 0x10000 > 0)
|
||||
result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;
|
||||
if (x & 0x8000 > 0)
|
||||
result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;
|
||||
if (x & 0x4000 > 0)
|
||||
result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;
|
||||
if (x & 0x2000 > 0)
|
||||
result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;
|
||||
if (x & 0x1000 > 0)
|
||||
result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;
|
||||
if (x & 0x800 > 0)
|
||||
result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;
|
||||
if (x & 0x400 > 0)
|
||||
result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;
|
||||
if (x & 0x200 > 0)
|
||||
result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;
|
||||
if (x & 0x100 > 0)
|
||||
result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;
|
||||
if (x & 0x80 > 0)
|
||||
result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;
|
||||
if (x & 0x40 > 0)
|
||||
result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;
|
||||
if (x & 0x20 > 0)
|
||||
result = result * 0x100000000000000162E42FEFA39EF366F >> 128;
|
||||
if (x & 0x10 > 0)
|
||||
result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;
|
||||
if (x & 0x8 > 0)
|
||||
result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;
|
||||
if (x & 0x4 > 0)
|
||||
result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;
|
||||
if (x & 0x2 > 0)
|
||||
result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;
|
||||
if (x & 0x1 > 0)
|
||||
result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;
|
||||
|
||||
result >>= uint256 (int256 (63 - (x >> 64)));
|
||||
require (result <= uint256 (int256 (MAX_64x64)));
|
||||
|
||||
return int128 (int256 (result));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate natural exponent of x. Revert on overflow.
|
||||
*
|
||||
* @param x signed 64.64-bit fixed point number
|
||||
* @return signed 64.64-bit fixed point number
|
||||
*/
|
||||
function exp (int128 x) internal pure returns (int128) {
|
||||
unchecked {
|
||||
require (x < 0x400000000000000000); // Overflow
|
||||
|
||||
if (x < -0x400000000000000000) return 0; // Underflow
|
||||
|
||||
return exp_2 (
|
||||
int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate x / y rounding towards zero, where x and y are unsigned 256-bit
|
||||
* integer numbers. Revert on overflow or when y is zero.
|
||||
*
|
||||
* @param x unsigned 256-bit integer number
|
||||
* @param y unsigned 256-bit integer number
|
||||
* @return unsigned 64.64-bit fixed point number
|
||||
*/
|
||||
function divuu (uint256 x, uint256 y) private pure returns (uint128) {
|
||||
unchecked {
|
||||
require (y != 0);
|
||||
|
||||
uint256 result;
|
||||
|
||||
if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
|
||||
result = (x << 64) / y;
|
||||
else {
|
||||
uint256 msb = 192;
|
||||
uint256 xc = x >> 192;
|
||||
if (xc >= 0x100000000) { xc >>= 32; msb += 32; }
|
||||
if (xc >= 0x10000) { xc >>= 16; msb += 16; }
|
||||
if (xc >= 0x100) { xc >>= 8; msb += 8; }
|
||||
if (xc >= 0x10) { xc >>= 4; msb += 4; }
|
||||
if (xc >= 0x4) { xc >>= 2; msb += 2; }
|
||||
if (xc >= 0x2) msb += 1; // No need to shift xc anymore
|
||||
|
||||
result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);
|
||||
require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
|
||||
uint256 hi = result * (y >> 128);
|
||||
uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
|
||||
uint256 xh = x >> 192;
|
||||
uint256 xl = x << 64;
|
||||
|
||||
if (xl < lo) xh -= 1;
|
||||
xl -= lo; // We rely on overflow behavior here
|
||||
lo = hi << 128;
|
||||
if (xl < lo) xh -= 1;
|
||||
xl -= lo; // We rely on overflow behavior here
|
||||
|
||||
assert (xh == hi >> 128);
|
||||
|
||||
result += xl / y;
|
||||
}
|
||||
|
||||
require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
|
||||
return uint128 (result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer
|
||||
* number.
|
||||
*
|
||||
* @param x unsigned 256-bit integer number
|
||||
* @return unsigned 128-bit integer number
|
||||
*/
|
||||
function sqrtu (uint256 x) private pure returns (uint128) {
|
||||
unchecked {
|
||||
if (x == 0) return 0;
|
||||
else {
|
||||
uint256 xx = x;
|
||||
uint256 r = 1;
|
||||
if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }
|
||||
if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }
|
||||
if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }
|
||||
if (xx >= 0x10000) { xx >>= 16; r <<= 8; }
|
||||
if (xx >= 0x100) { xx >>= 8; r <<= 4; }
|
||||
if (xx >= 0x10) { xx >>= 4; r <<= 2; }
|
||||
if (xx >= 0x4) { r <<= 1; }
|
||||
r = (r + x / r) >> 1;
|
||||
r = (r + x / r) >> 1;
|
||||
r = (r + x / r) >> 1;
|
||||
r = (r + x / r) >> 1;
|
||||
r = (r + x / r) >> 1;
|
||||
r = (r + x / r) >> 1;
|
||||
r = (r + x / r) >> 1; // Seven iterations should be enough
|
||||
uint256 r1 = x / r;
|
||||
return uint128 (r < r1 ? r : r1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
28
solidity/aux/LICENSE.md
Normal file
28
solidity/aux/LICENSE.md
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2019, [ABDK Consulting](https://abdk.consulting/)
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. All advertising materials mentioning features or use of this software must
|
||||
display the following acknowledgement: This product includes software
|
||||
developed by ABDK Consulting.
|
||||
4. Neither the name of ABDK Consulting nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY ABDK CONSULTING ''AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL ABDK CONSULTING BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
Loading…
Reference in New Issue
Block a user