erc20-demurrage-token

ERC20 token with redistributed continual demurrage
Log | Files | Refs | README

commit 76e8170014f8314f25620281abd4d120f36a64b5
parent 091967bafdad4e1b3f5e168a4475c648f9dcfdf5
Author: lash <dev@holbrook.no>
Date:   Tue, 16 May 2023 11:21:38 +0100

Add makefiles, update readme

Diffstat:
AMakefile | 12++++++++++++
MREADME.md | 419+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Adoc/texinfo/Makefile | 4++++
Mdoc/texinfo/contract.texi | 10+++++++---
Apython/Makefile | 2++
5 files changed, 371 insertions(+), 76 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,12 @@ +all: python solidity doc +python: + make -C python +solidity: + make -C solidity +doc: + make -C doc/texinfo +readme: + make -C doc/texinfo readme + pandoc -f docbook -t gfm doc/texinfo/build/docbook.xml > README.md + + diff --git a/README.md b/README.md @@ -1,131 +1,404 @@ -# RedistributedDemurrageToken - -## Use Case -* 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% (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. +# Overview + + de-mur-rage + + 1: the detention of a ship by the freighter beyond the time allowed for loading, unloading, or sailing + + 2: a charge for detaining a ship, freight car, or truck + +This ERC20 smart contract implementation for the EVM imposes a demurrage +on all held token balances. + +The demurrage is a continuous value *decay*, subtracted from all +balances every minute. + +Also. a time period is defined at contract creation time at which the +difference between held balances and the demurrage can be withdrawn to a +pre-selected address, which in turn can redistribute that token value. + +In short: Everyone is taxed a little something every minute, and every +so often a decision is made on how to redistribute that tax. + +## Features + +- Continuous decay of all balances. + +- Capture and redistribution of decayed balances. + +- Per-minute decay resolution. + +- Minting and burning of vouchers. + +- Grant and revoke access to mint and burn vouchers. + +- Voucher expiration (modifiable anytime after publishing). + +- Supply cap (modifiable anytime after publishing). + +- Constant gas usage across exponential calculations. ## Nomenclature -* `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. +‘`Demurrage`’ +A percentage of token supply that will continuously be removed. + +‘`Demurrage Period`’ +A period of time denominated in minutes after which demurraged amounts +are available for redistribution. + +‘`Sink Account`’ +The intermediate beneficiary of the demurraged amount, which may or may +not redistribute value. + +‘`Base balance`’ +The inflated balance of each used which is stored for bookkeeping. + +# Use Case + +The use-case inspiring this implementation is in the context of issuance +of a *voucher* representing a credit obligation of an *Issuer* or +*Association of Issuers*. + +This voucher can be redeemed as payment for the products of the Issuer. + +The Issuer is the entity legally obligated to redeem the voucher as +payment. + +Introducing demurrage on this vehicle discourages *withholding* the +voucher, for example for speculative advantage. + +This also encourages increased *velocity* of voucher use. + +## Example + +Given: + +- 10 voucher holders. + +- A total supply of 1000 tokens. + +- Demurrage of 2% per 30 days (43200 minutes). + +- Redistribution period of 30 days (43200 minutes). + +If no trades are made, the resulting balances after one redistribution +period of every user would be 98 Vouchers. + +The Sink Address will have a balance of 20 vouchers after the same +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 +(even the Sink Account, pending redistribution). + +# Smart contract + +## Common interfaces + +The smart contract is written in solidity, compatible with 0.8.x. + +It implements a number of interfaces both from the Ethereum (ERC) +standards aswell as the Community Inclusion Currency contract interface +suite. + +### ERC standard interfaces + +- [ERC20 - Token Standard](https://eips.ethereum.org/EIPS/eip-20) + +- [ERC165 - Standard Interface + Detection](https://eips.ethereum.org/EIPS/eip-165) + +- [ERC173 - Contract Ownership + Standard](https://eips.ethereum.org/EIPS/eip-173) + +- [ERC5679 - Token Minting and Burning (as part of CIC.Minter and + CIC.Burner)](https://eips.ethereum.org/EIPS/eip-5679) + +### CIC interfaces + +- [Burner](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Burner.sol) + +- [Expire](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Expire.sol) +- [Minter](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Minter.sol) -## Ownership +- [Seal](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Seal.sol) -* Contract creator is owner -* Ownership can be transferred +- [Writer](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Writer.sol) +## Dependencies -## Mint +The token contract uses the +[ADBKMath](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol) +library to calculate exponentials. -* 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. +## Permissions +The smart contract defines three levels of access. -## Input parameters +1. Voucher contract owner -The redistrbution period is passed to the contract in minutes. E.g. a redistribution period of one month would be approximately 43200 minutes. +2. Voucher minter -The demurrage level specified as the percentage of continuous growth per minute: +3. Voucher holder -`(1 - percentage) ^ (1 / period)` +### Contract owner -E.g. A demurrage of 2% monthly would be defined as: +When the contract is published to the network, the signer account of the +publishing transaction will be the contract owner. -`(1 - 0.02) ^ (1 / 43200) ~ 0.99999953234484737109` +Contract ownership can be changed by the owner using the **ERC173** +standard interface. -The number must be provided to the contract as a 64x64 bit fixed-point number (where the integer part is 0). +### Minter -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. +A minter has access to mint vouchers, and to burn vouchers from its own +balance. +Only the contract owner may mint, and may add and remove minters. +Minters may be added and removed using the **CIC Writer** interface, as +long as the `WRITER_STATE` seal is not set. See [Sealing the +contract](#seal_005fstate) for further details. -## Demurrage calculation +The contract owner is automatically a minter. -The demurrage calculation inside the contract is done by the following formula, where `demurrageLevel` is the demurrage level input parameter of the contract: +### Holder -`newDemurrageModifier = currentDemurrageModifier * (e ^ (ln(demurrageLevel) * minutes))` +Any address may hold vouchers, and transfer vouchers from their balance. -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. +Minters and the contract owner are automatically token holders. +All token holders are subject to demurrage. -All client-facing values (_balance output_ , _transfer inputs_) are adjusted with `demurrageModifier`. +## Publishing the contract -e.g. `_balance output_ = user_balance - user_balance * demurrageModifier` +The contract is published with the following arguments: +‘`name`’ +ERC20 voucher name -## Redistribution +‘`symbol`’ +ERC20 voucher symbol -* 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. -* Redistributed tokens are added to the balance of the _sink address_ given when the contract is published. -* _sink address_ may be changed. +‘`decimals`’ +ERC20 decimal count +‘`decayLevel`’ +Level of decay per minute. See [Specifying +demurrage](#specifying_005fdemurrage) below for further details. -## Data representation +‘`periodMinutes`’ +Number of minutes between each time the demurraged value can be +withdrawn to the *Sink Account*. See [Withdrawing demurraged +value](#withdrawing) below for further details. The period may not be +altered. -Token parameters are truncated when calculating demurrage and redistribution: +‘`defaultSinkAddress`’ +The initial *Sink Address*. The address may be altered as long as the +`SINK_STATE` seal has not been set. See [Sealing the +contract](#seal_005fstate) for further details. -* Redistribution period: 32 bits -* Token supply: 72 bits -* Demurrage modifier: 64 bits +### Specifying demurrage +The *input parameter* to the contract is a 128-bit positive fixed-point +number, where the most significant 64 bits represent the integer part, +and the lower 64 bits represents the decimals part, each consecutive +lesser bit halving the value of the previous bit. -## Expiration +For example, The byte value `00000000 00000002 a0000000 00000000`, +representing a zero-stripped binary value of $10.101$, translates to the +(base 10) decimal value $2.625$. -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. +#### Calculating the demurrage parameter -Expiration may be set in terms of redistribution periods. +The minute granularity of the demurrage value is calculating using the +continuous decay function. -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. +For example, for a demurrage of 2% per 30 days (43200 minutes), the +input value will be: +$(1-0.02)^(1/43200) ~ 0.99999953234484737109$ -## Supply +The decimal part of the fixed-point representation of this value is: -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. +`fffff8276fb8cfff` +The input parameter becomes: -## Mutability +`0000000000000000ffffa957014dc7ff` -The following parameters may not be changed after contract is published: +See [Tools](#tools) for additional help generating the necessary values. -* Demurrage level -* Redistribution period +Note that attempting to publish a voucher contract with no (zero) +demurrage will fail (if demurrage is not needed, use another contract). -The contract provides a sealing feature which prohibits further changes to parameters that can initially be edited. These include: +## Using the contract -* Adding and removing writers (addresses that may mint tokens) -* Sink addres -* Expiry period -* Supply limit +### Withdrawing demurrage +After each redistribution period, the demurraged value of that period +can be withdrawn to the currently defined *Sink Account*. + +The demurrage is calculated as from the total supply of voucher at the +end of the period. + +Withdrawal should happen implicitly duing normal operation of the +contract. See [Side-effects in state changes](#sideeffects). + +To explicitly credit the *Sink Address* with the demurrage value after a +period has been exceeded, the `changePeriod()` (`8f1df6bc`) method can +be called. + +### Setting voucher expiry + +The effect of a voucher expiring is that all balances will be frozen, +and all state changes affecting token balances will be blocked. + +Expiry is defined in terms of redistribution periods. For example, if +the redistribution period is 30 days, and the expity is 3, then the +voucher expires after 90 days. + +The expiry takes effect immediately when the redistribution period time +has been exceeded. + +When the contract is published, no expiry is set. + +Expiry may be set after publishing using the `CIC.Expire` interface. + +If the `EXPIRE_STATE` seal has been set, expiry may not be changed +further. + +### Capping voucher supply + +The effect of a voucher supply cap is that all `CIC.Minter` calls will +fail if the total supply after minting exceeds the defined supply cap. + +The supply cap still allows vouchers to be minted after `CIC.Burn` +calls, provided that the previous condition holds. + +To apply the supply cap, the method `setMaxSupply(uint256) (6f8b44b0)` +is used. + +### Side-effects in state changes + +All state changes involving voucher values implicitly execute two core +methods to ensure application of the demurrage and redistribution. + +The two methods are: + +`applyDemurrage() (731f237c)` +Calculates the demurrage modifier of all balances according to the +current timestamp. + +`changePeriod() (8f1df6bc)` +If the previously executed period change does not match the current +period, the period is changed, and the *Sink Address* is credited with +the demurrage amount of the current total supply. + +Both of these methods are *noop* if no demurrage or withdrawal is +pending, respectively. + +Examples of state changes that execute these methods include +`ERC20.transfer(...)`, `ERC20.transferFrom(...)` and `CIC.mintTo(...)`. + +### Sealing the contract + +Certain mutable core parameters of the contract can be *sealed*, meaning +prevented from being modifier further. + +Sealing is executed using the `CIC.Seal` interface. + +The sealing of parameters is irreversible. + +The sealable parameters are[^1]: + +`WRITER_STATE` +The `CIC.Writer` interface is blocked. The effect of this is that no +more changes may be made to which accounts have minter permission. + +`SINK_STATE` +After setting this seal, the *Sink Address* may not be changed. + +`EXPIRY_STATE` +Prevents future changes to the voucher expiry date[^2]. + +`CAP_STATE` +Immediately prevents future voucher minting, regardless of permissions. ## 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. + +## Caveats + +A `ERC20.transferFrom(...)` following an `ERC20.approve(...)` call, when +called across period thresholds, may fail if margin to demurraged amount +is insufficient. + +# Tools + +When installed as a python package, `erc20-demurrage-token` installs the +`erc20-demurrage-token-publish` executable script, which can be used to +publish smart contract instances. + +While the man page for the tool can be referred to for general +information of the tool usage, two argument flags warrant special +mention in the context of this documentation. + +`--demurrage-level` +The percentage of demurrage in terms of the redistribution period, +defined as parts-per-million. + +`--redistribution-period` +A numeric value denominated in *minutes* to define the redistribution +period of the voucher demurrage. + +For example, to define a 2% demurrage value for a redistribution period +of 30 days (43200 minutes), the argument to the argument flags would be: + + erc20-demurrage-token-publish --demurrage-level 20000 --redistribution-period 43200 ... + +## Calculating fixed-point values + +The `erc20-demurrage-token` package installs the python package `dexif` +as part of its dependencies. + +This package in turn provides an epinymous command-line tool (`dexif`) +which converts decimal values to a 128-bit fixed-point value expected by +the contract constructor. + +An example: + + $ dexif 123.456 + 7b74bc6a7ef9db23ff + + $ dexif -x 7b74bc6a7ef9db23ff + 123.456 + +## Contract interaction with chainlib-eth + +All smart contract tests are implementing using +[chainlib-eth](https://git.defalsify.org/chainlib-eth) from the +chaintool suite. -Gas usage is constant regardless of the amount of time passed between each execution of demurrage and redistribution period calculations. +The `eth-encode` tool from the `chainlib-eth` python package may be a +convenient way to interact with contract features. +Some examples include: -## QA + # explicitly call changePeriod() + $ eth-encode --mode tx --signature changePeriod -e <contract_address> -y <key_file> ... -* Tests are implemented using the `chaintool` python package suite. + # Set the sink address seal (The integer value of the SINK_STATE flag is 2 at the time of writing) + $ eth-encode --mode tx --signature seal -e <contract_address> -y <key_file> ... u:2 + # Query current sink address of contract + $ eth-encode --mode call --signature sinkAddress -e <contract_address> ... -## Known issues +[^1]: Please refer to the contract source code for the numeric values of + the state flags -* A `transferFrom` following an `approve` call, when called across period thresholds, may fail if margin to demurraged amount is insufficient. +[^2]: The `EXPIRY_STATE` is implicitly set after expiration. diff --git a/doc/texinfo/Makefile b/doc/texinfo/Makefile @@ -0,0 +1,4 @@ +doc: + makeinfo --html -o build index.texi +readme: + makeinfo --docbook -o build/docbook.xml index.texi diff --git a/doc/texinfo/contract.texi b/doc/texinfo/contract.texi @@ -123,7 +123,7 @@ The input parameter becomes: @code{0000000000000000ffffa957014dc7ff} -@xref{tools, Useful tools} for additional help generating the necessary values. +@xref{tools, Tools} for additional help generating the necessary values. Note that attempting to publish a voucher contract with no (zero) demurrage will fail (if demurrage is not needed, use another contract). @@ -135,7 +135,9 @@ Note that attempting to publish a voucher contract with no (zero) demurrage will After each redistribution period, the demurraged value of that period can be withdrawn to the currently defined @emph{Sink Account}. -The demurrage is calculated as from the total supply of voucher at the end of the period. @xref{sideeffects, Side-effects in state changes}. +The demurrage is calculated as from the total supply of voucher at the end of the period. + +Withdrawal should happen implicitly duing normal operation of the contract. @xref{sideeffects, Side-effects in state changes}. To explicitly credit the @emph{Sink Address} with the demurrage value after a period has been exceeded, the @code{changePeriod()} (@code{8f1df6bc}) method can be called. @@ -163,7 +165,7 @@ The effect of a voucher supply cap is that all @code{CIC.Minter} calls will fail The supply cap still allows vouchers to be minted after @code{CIC.Burn} calls, provided that the previous condition holds. -To apply the supply cap, the method @code{maxSupply(uint256) (869f7594)} is used. +To apply the supply cap, the method @code{setMaxSupply(uint256) (6f8b44b0)} is used. @node sideeffects @@ -180,6 +182,8 @@ Calculates the demurrage modifier of all balances according to the current times If the previously executed period change does not match the current period, the period is changed, and the @emph{Sink Address} is credited with the demurrage amount of the current total supply. @end table +Both of these methods are @emph{noop} if no demurrage or withdrawal is pending, respectively. + Examples of state changes that execute these methods include @code{ERC20.transfer(...)}, @code{ERC20.transferFrom(...)} and @code{CIC.mintTo(...)}. diff --git a/python/Makefile b/python/Makefile @@ -0,0 +1,2 @@ +all: + python setup.py sdist