Compare commits

..

No commits in common. "dev-0.5.6" and "v0.5.1" have entirely different histories.

23 changed files with 85 additions and 1072 deletions

4
CAVEAT
View File

@ -1 +1,5 @@
The contract is intended for slow rates of decay (e.g. 2% per month). Very high levels of decay (2% per minute) will lead to overflows, and will need a more flexible implementation to support it. The contract is intended for slow rates of decay (e.g. 2% per month). Very high levels of decay (2% per minute) will lead to overflows, and will need a more flexible implementation to support it.
The contract is written with frequent usage in mind. If used for tokens with low usage freqency (e.g. several days idle), it is recommended to run a continuous process triggering the changePeriod() contract call, to reduce the amount of exponential calculation the application of demurrage will trigger.
When changing the period, 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.

View File

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

418
README.md
View File

@ -1,405 +1,131 @@
# Overview # RedistributedDemurrageToken
de-mur-rage ## Use Case
* Vouchers
1: the detention of a ship by the freighter beyond the time allowed for loading, unloading, or sailing * 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.
2: a charge for detaining a ship, freight car, or truck * 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.
This ERC20 smart contract implementation for the EVM imposes a demurrage * Example
on all held token balances. - 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, its balance would again reach 20 the next redistribution period.
The demurrage is a continuous value *decay*, subtracted from all - Note that after the redistribution the total of all balances will equal the total minted amount.
balances every minute. - Note that all accounts holding such Vouchers are effected by demurrage.
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 ## Nomenclature
`Demurrage` * `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.
A percentage of token supply that will continuously be removed. * 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 Period`
A period of time denominated in minutes after which demurraged amounts
are available for redistribution.
`Sink Account` ## Ownership
The intermediate beneficiary of the demurraged amount, which may or may
not redistribute value.
`Base balance` * Contract creator is owner
The inflated balance of each used which is stored for bookkeeping. * Ownership can be transferred
# Use Case
The use-case inspiring this implementation is in the context of issuance ## Mint
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. * 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.
The Issuer is the entity legally obligated to redeem the voucher as
payment.
Introducing demurrage on this vehicle discourages *withholding* the ## Input parameters
voucher, for example for speculative advantage.
This also encourages increased *velocity* of voucher use. The redistrbution period is passed to the contract in minutes. E.g. a redistribution period of one month would be approximately 43200 minutes.
## Example The demurrage level specified as the percentage of continuous growth per minute:
Given: `(1 - percentage) ^ (1 / period)`
- 10 voucher holders. E.g. A demurrage of 2% monthly would be defined as:
- A total supply of 1000 tokens. `(1 - 0.02) ^ (1 / 43200) ~ 0.99999953234484737109`
- Demurrage of 2% per 30 days (43200 minutes). The number must be provided to the contract as a 64x64 bit fixed-point number (where the integer part is 0).
- Redistribution period of 30 days (43200 minutes). 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.
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 ## Demurrage calculation
period.
Note that after the redistribution the total of all balances will equal The demurrage calculation inside the contract is done by the following formula, where `demurrageLevel` is the demurrage level input parameter of the contract:
the total minted amount.
Note that all accounts holding such vouchers are effected by demurrage `newDemurrageModifier = currentDemurrageModifier * (e ^ (ln(demurrageLevel) * minutes))`
(even the Sink Account, pending redistribution).
# Smart contract 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.
## Common interfaces
The smart contract is written in solidity, compatible with 0.8.x. All client-facing values (_balance output_ , _transfer inputs_) are adjusted with `demurrageModifier`.
It implements a number of interfaces both from the Ethereum (ERC) e.g. `_balance output_ = user_balance - user_balance * demurrageModifier`
standards aswell as the Community Inclusion Currency contract interface
suite.
### ERC standard interfaces
- [ERC20 - Token Standard](https://eips.ethereum.org/EIPS/eip-20) ## Redistribution
- [ERC165 - Standard Interface * One redistribution entry is added to storage for each `period`;
Detection](https://eips.ethereum.org/EIPS/eip-165) * 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.
- [ERC173 - Contract Ownership
Standard](https://eips.ethereum.org/EIPS/eip-173)
- [ERC5679 - Token Minting and Burning (as part of CIC.Minter and ## Data representation
CIC.Burner)](https://eips.ethereum.org/EIPS/eip-5679)
### CIC interfaces Token parameters are truncated when calculating demurrage and redistribution:
- [Burner](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Burner.sol) * Redistribution period: 32 bits
* Token supply: 72 bits
* Demurrage modifier: 64 bits
- [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) ## Expiration
- [Seal](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Seal.sol) 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.
- [Writer](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Writer.sol) Expiration may be set in terms of redistribution periods.
## Dependencies 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.
The token contract uses the
[ADBKMath](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol)
library to calculate exponentials.
## Permissions ## Supply
The smart contract defines three levels of access. 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.
1. Voucher contract owner
2. Voucher minter ## Mutability
3. Voucher holder The following parameters may not be changed after contract is published:
### Contract owner * Demurrage level
* Redistribution period
When the contract is published to the network, the signer account of the The contract provides a sealing feature which prohibits further changes to parameters that can initially be edited. These include:
publishing transaction will be the contract owner.
Contract ownership can be changed by the owner using the **ERC173** * Adding and removing writers (addresses that may mint tokens)
standard interface. * Sink addres
* Expiry period
* Supply limit
### Minter
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.
The contract owner is automatically a minter.
### Holder
Any address may hold vouchers, and transfer vouchers from their balance.
Minters and the contract owner are automatically token holders.
All token holders are subject to demurrage.
## Publishing the contract
The contract is published with the following arguments:
`name`
ERC20 voucher name
`symbol`
ERC20 voucher symbol
`decimals`
ERC20 decimal count
`decayLevel`
Level of decay per minute. See [Specifying
demurrage](#specifying_005fdemurrage) below for further details.
`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.
`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.
### 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.
For example, The byte value `00000000 00000002 a0000000 00000000`,
representing a zero-stripped binary value of $10.101$. This translates
to the (base 10) decimal value $2.625$. The decimal part is calculated
as, from left to right: $(1 * 0.5) + (0 * 0.25) + (1 * 0.125)$.
#### Calculating the demurrage parameter
The minute granularity of the demurrage value is calculating using the
continuous decay function.
For example, for a demurrage of 2% per 30 days (43200 minutes), the
input value will be:
$(1-0.02)^(1/43200) ~ 0.99999953234484737109$
The decimal part of the fixed-point representation of this value is:
`fffff8276fb8cfff`
The input parameter becomes:
`0000000000000000ffffa957014dc7ff`
See [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).
## Using the contract
### 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 ## Gas usage
Gas usage is constant regardless of the amount of time passed between The token contract uses the [ADBKMath](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol) library to calculate exponentials.
each execution of demurrage and redistribution period calculations.
## Caveats Gas usage is constant regardless of the amount of time passed between each execution of demurrage and redistribution period calculations.
A `ERC20.transferFrom(...)` following an `ERC20.approve(...)` call, when
called across period thresholds, may fail if margin to demurraged amount
is insufficient.
# Tools ## QA
When installed as a python package, `erc20-demurrage-token` installs the * Tests are implemented using the `chaintool` python package suite.
`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` ## Known issues
The percentage of demurrage in terms of the redistribution period,
defined as parts-per-million.
`--redistribution-period` * A `transferFrom` following an `approve` call, when called across period thresholds, may fail if margin to demurraged amount is insufficient.
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.
The `eth-encode` tool from the `chainlib-eth` python package may be a
convenient way to interact with contract features.
Some examples include:
# explicitly call changePeriod()
$ eth-encode --mode tx --signature changePeriod -e <contract_address> -y <key_file> ...
# 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> ...
[^1]: Please refer to the contract source code for the numeric values of
the state flags
[^2]: The `EXPIRY_STATE` is implicitly set after expiration.

View File

@ -1,4 +0,0 @@
doc:
makeinfo --html -o build index.texi
readme:
makeinfo --docbook -o build/docbook.xml index.texi

View File

@ -1,221 +0,0 @@
@node contract
@chapter Smart contract
@section 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.
@subsection ERC standard interfaces
@itemize @bullet
@item
@uref{https://eips.ethereum.org/EIPS/eip-20, ERC20 - Token Standard}
@item
@uref{https://eips.ethereum.org/EIPS/eip-165, ERC165 - Standard Interface Detection}
@item
@uref{https://eips.ethereum.org/EIPS/eip-173, ERC173 - Contract Ownership Standard}
@item
@uref{https://eips.ethereum.org/EIPS/eip-5679, ERC5679 - Token Minting and Burning (as part of CIC.Minter and CIC.Burner)}
@end itemize
@subsection CIC interfaces
@itemize @bullet
@item
@uref{https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Burner.sol, Burner}
@item
@uref{https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Expire.sol, Expire}
@item
@uref{https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Minter.sol, Minter}
@item
@uref{https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Seal.sol, Seal}
@item
@uref{https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Writer.sol, Writer}
@end itemize
@section Dependencies
The token contract uses the @url{https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol, ADBKMath} library to calculate exponentials.
@section Permissions
The smart contract defines three levels of access.
@enumerate
@item Voucher contract owner
@item Voucher minter
@item Voucher holder
@end enumerate
@subsection Contract owner
When the contract is published to the network, the signer account of the publishing transaction will be the contract owner.
Contract ownership can be changed by the owner using the @strong{ERC173} standard interface.
@subsection Minter
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 @strong{CIC Writer} interface, as long as the @code{WRITER_STATE} seal is not set. @xref{seal_state, Sealing the contract} for further details.
The contract owner is automatically a minter.
@subsection Holder
Any address may hold vouchers, and transfer vouchers from their balance.
Minters and the contract owner are automatically token holders.
All token holders are subject to demurrage.
@section Publishing the contract
The contract is published with the following arguments:
@table @samp
@item name
ERC20 voucher name
@item symbol
ERC20 voucher symbol
@item decimals
ERC20 decimal count
@item decayLevel
Level of decay per minute. @xref{specifying_demurrage, Specifying demurrage} below for further details.
@item periodMinutes
Number of minutes between each time the demurraged value can be withdrawn to the @emph{Sink Account}. @xref{withdrawing, Withdrawing demurraged value} below for further details. The period may not be altered.
@item defaultSinkAddress
The initial @emph{Sink Address}. The address may be altered as long as the @code{SINK_STATE} seal has not been set. @xref{seal_state, Sealing the contract} for further details.
@end table
@node specifying_demurrage
@subsection Specifying demurrage
The @emph{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.
For example, The byte value @code{00000000 00000002 a0000000 00000000}, representing a zero-stripped binary value of @math{10.101}. This translates to the (base 10) decimal value @math{2.625}. The decimal part is calculated as, from left to right: @math{(1 * 0.5) + (0 * 0.25) + (1 * 0.125)}.
@subsubsection Calculating the demurrage parameter
The minute granularity of the demurrage value is calculating using the continuous decay function.
For example, for a demurrage of 2% per 30 days (43200 minutes), the input value will be:
@math{(1-0.02)^(1/43200) ~ 0.99999953234484737109}
The decimal part of the fixed-point representation of this value is:
@code{fffff8276fb8cfff}
The input parameter becomes:
@code{0000000000000000ffffa957014dc7ff}
@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).
@section Using the contract
@node withdrawing
@subsection Withdrawing demurrage
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.
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.
@node expiry
@subsection 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 @code{CIC.Expire} interface.
If the @code{EXPIRE_STATE} seal has been set, expiry may not be changed further.
@node supply
@subsection Capping voucher supply
The effect of a voucher supply cap is that all @code{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 @code{CIC.Burn} calls, provided that the previous condition holds.
To apply the supply cap, the method @code{setMaxSupply(uint256) (6f8b44b0)} is used.
@node sideeffects
@subsection 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:
@table @code
@item applyDemurrage() (731f237c)
Calculates the demurrage modifier of all balances according to the current timestamp.
@item changePeriod() (8f1df6bc)
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(...)}.
@node seal_state
@subsection Sealing the contract
Certain mutable core parameters of the contract can be @emph{sealed}, meaning prevented from being modifier further.
Sealing is executed using the @code{CIC.Seal} interface.
The sealing of parameters is irreversible.
The sealable parameters are@footnote{Please refer to the contract source code for the numeric values of the state flags}:
@table @code
@item WRITER_STATE
The @code{CIC.Writer} interface is blocked. The effect of this is that no more changes may be made to which accounts have minter permission.
@item SINK_STATE
After setting this seal, the @emph{Sink Address} may not be changed.
@item EXPIRY_STATE
Prevents future changes to the voucher expiry date@footnote{The @code{EXPIRY_STATE} is implicitly set after expiration.}.
@item CAP_STATE
Immediately prevents future voucher minting, regardless of permissions.
@end table
@section Gas usage
Gas usage is constant regardless of the amount of time passed between each execution of demurrage and redistribution period calculations.
@section Caveats
A @code{ERC20.transferFrom(...)} following an @code{ERC20.approve(...)} call, when called across period thresholds, may fail if margin to demurraged amount is insufficient.

View File

@ -1,29 +0,0 @@
\input texinfo
@settitle ERC20 Demurrage Token
@copying
Released 2023 under AGPL3
@end copying
@titlepage
@title ERC20 Demurrage Token
@author Louis Holbrook
@end titlepage
@c
@contents
@ifnottex
@node Top
@top Introduction
@end ifnottex
@menu
* overview ::
* instructions :
@end menu
@include overview.texi
@include usecase.texi
@include contract.texi
@include tools.texi

View File

@ -1,47 +0,0 @@
@node overview
@chapter Overview
@verbatim
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
@end verbatim
This ERC20 smart contract implementation for the EVM imposes a demurrage on all held token balances.
The demurrage is a continuous value @emph{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.
@section Features
@itemize
@item Continuous decay of all balances.
@item Capture and redistribution of decayed balances.
@item Per-minute decay resolution.
@item Minting and burning of vouchers.
@item Grant and revoke access to mint and burn vouchers.
@item Voucher expiration (modifiable anytime after publishing).
@item Supply cap (modifiable anytime after publishing).
@item Constant gas usage across exponential calculations.
@end itemize
@section Nomenclature
@table @samp
@item Demurrage
A percentage of token supply that will continuously be removed.
@item Demurrage Period
A period of time denominated in minutes after which demurraged amounts are available for redistribution.
@item Sink Account
The intermediate beneficiary of the demurraged amount, which may or may not redistribute value.
@item Base balance
The inflated balance of each used which is stored for bookkeeping.
@end table

View File

@ -1,57 +0,0 @@
@node tools
@chapter Tools
When installed as a python package, @code{erc20-demurrage-token} installs the @code{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.
@table @code
@item --demurrage-level
The percentage of demurrage in terms of the redistribution period, defined as parts-per-million.
@item --redistribution-period
A numeric value denominated in @emph{minutes} to define the redistribution period of the voucher demurrage.
@end table
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:
@verbatim
erc20-demurrage-token-publish --demurrage-level 20000 --redistribution-period 43200 ...
@end verbatim
@section Calculating fixed-point values
The @code{erc20-demurrage-token} package installs the python package @code{dexif} as part of its dependencies.
This package in turn provides an epinymous command-line tool (@code{dexif}) which converts decimal values to a 128-bit fixed-point value expected by the contract constructor.
An example:
@example
$ dexif 123.456
7b74bc6a7ef9db23ff
$ dexif -x 7b74bc6a7ef9db23ff
123.456
@end example
@section Contract interaction with chainlib-eth
All smart contract tests are implementing using @url{https://git.defalsify.org/chainlib-eth, chainlib-eth} from the chaintool suite.
The @code{eth-encode} tool from the @code{chainlib-eth} python package may be a convenient way to interact with contract features.
Some examples include:
@example
# explicitly call changePeriod()
$ eth-encode --mode tx --signature changePeriod -e <contract_address> -y <key_file> ...
# 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> ...
@end example

View File

@ -1,32 +0,0 @@
@node usecase
@chapter Use Case
The use-case inspiring this implementation is in the context of issuance of a @emph{voucher} representing a credit obligation of an @emph{Issuer} or @emph{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 @emph{withholding} the voucher, for example for speculative advantage.
This also encourages increased @emph{velocity} of voucher use.
@section Example
Given:
@itemize
@item 10 voucher holders.
@item A total supply of 1000 tokens.
@item Demurrage of 2% per 30 days (43200 minutes).
@item Redistribution period of 30 days (43200 minutes).
@end itemize
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).

View File

@ -1,17 +1,3 @@
- 0.5.5
* Make allowance method public
- 0.5.4
* Add Transfer() event emission to sweep() in contract
- 0.5.3
* Add texinfo documentation
* Add man page for publish tool
- 0.5.2
* Update ERC165 interface response for Expire
* Add ExpiryChange event
- 0.5.1
* Update ERC165 interface responses
- 0.5.0
* Change license
- 0.4.2 - 0.4.2
* Correct burn interface implementation * Correct burn interface implementation
- 0.4.1 - 0.4.1

View File

@ -1 +1 @@
include erc20_demurrage_token/data/* erc20_demurrage_token/data/config/*.ini *requirements.txt man/build/*.1 include erc20_demurrage_token/data/* erc20_demurrage_token/data/config/*.ini *requirements.txt

View File

@ -1,8 +0,0 @@
all: pypi man
pypi:
python setup.py sdist
man:
mkdir -vp man/build
chainlib-man.py -b 0x3fafff -d man/build -n erc20-demurrage-token-publish -v man

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

View File

@ -1,218 +0,0 @@
.TH erc20-demurrage-token-publish 1
.SH NAME
erc20-demurrage-token-publish \- Publishing tool for the ERC20 Demurrage Token smart contract.
.SH SYNOPSIS
.SY erc20-demurrage-token-publish
.RI --name NAME --symbol SYMBOL --decimals DECIMALS --demurrage-level PPM --redistribution-period MINUTES --sink-address ADDRESS
.YS
.SH DESCRIPTION
.P
This tool generates the appropriate bytecode to store the ERC20 Demurrage Token smartcontract code on an EVM chain, along with the required construction parameters.
Arguments for EVM encoding and interaction with EVM RPC node is handled by the \fBchainlib-eth\fP python package.
Specific arguments for this tool are the \fB--demurrage-level\fP, \fB--redistribution-period\fP and \fB--sink-address\fP arguments.
.SS OPTIONS
.TP
\fB-0\fP
Omit newline to output
.TP
\fB-c \fI\fIconfig_dir\fP\fP, \fB--config \fI\fIconfig_dir\fP\fP
Load configuration files from given directory. All files with an .ini extension will be loaded, of which all must contain valid ini file data.
.TP
\fB--demurrage-level \fI\fIlevel
\fP\fP
Level of decay per minute. See SPECIFYING DEMURRAGE below.
.TP
\fB--dumpconfig \fI\fIformat\fP\fP
Output configuration settings rendered from environment and inputs. Valid arguments are \fIini\fP for ini file output, and \fIenv\fP for environment variable output. See \fBCONFIGURATION\fP.
.TP
\fB-e\fP, \fB--executable-address\fP
Address of an executable code point on the network.
.TP
\fB--env-prefix\fP
Environment prefix for variables to overwrite configuration. Example: If \fB--env-prefix\fP is set to \fBFOO\fP then configuration variable \fBBAR_BAZ\fP would be set by environment variable \fBFOO_BAZ_BAR\fP. Also see \fBENVIRONMENT\fP.
.TP
\fB--fee-limit\fP
Set the limit of execution units for the transaction. If used with \fB-s\fP this may incur actual network token cost. If \fB--fee-price\fP is not explicitly set, the price \fImay\fP be retrieved from the network, and multiplied with this value to define the cost.
.TP
\fB--fee-price\fP
Set fee unit price to offer for the transaction. If used with \fB-s\fP this may incur actual network token cost.
.TP
\fB--height\fP
Block height at which to query state for. Does not apply to transactions.
.TP
\fB-i \fI\fIchain_spec\fP\fP, \fB--chain-spec \fI\fIchain_spec\fP\fP
Chain specification string, in the format <engine>:<fork>:<chain_id>:<common_name>. Example: "evm:london:1:ethereum". Overrides the \fIRPC_CREDENTIALS\fP configuration setting.
.TP
\fB-n \fI\fInamespace\fP\fP, \fB--namespace \fI\fInamespace\fP\fP
Load given configuration namespace. Configuration will be loaded from the immediate configuration subdirectory with the same name.
.TP
\fB--no-logs\fP
Turn of logging completely. Negates \fB-v\fP and \fB-vv\fP
.TP
\fB--nonce\fP
Explicitly set nonce to use for transaction.
.TP
\fB-p\fP, \fB--rpc-provider\fP
Fully-qualified URL of RPC provider. Overrides the \fIRPC_PROVIDER\fP configuration setting.
.TP
\fB--passphrase-file \fI\fIpath\fP\fP
Path to file containing password to unlock key file
.TP
\fB--raw\fP
Produce output most optimized for machines.
.TP
\fB--redistribution-period \fI\fIminutes
\fP\fP
Number of minuntes between each withdrawal of demurraged value is possible.
.TP
\fB--rpc-auth\fP
RPC endpoint authentication method, e.g. how to handle a HTTP WWW-Authenticate header. Overrides the \fIRPC_AUTH\fP configuration setting.
.TP
\fB--rpc-credentials\fP
RPC endpoint authentication data. Format depends on the authentication method defined in \fB--rpc-auth\fP. Overrides the \fIRPC_CREDENTIALS\fP configuration setting.
.TP
\fB--rpc-dialect\fP
RPC backend dialect. If specified it \fImay\fP help with encoding and decoding issues. Overrides the \fIRPC_DIALECT\fP configuration setting.
.TP
\fB-s\fP
Send to network. If set, network state may change. This means tokens may be spent and so on. Use with care. Only applies to transactions.
.TP
\fB--seq\fP
Use numeric sequencial jsonrpc query ids. Useful for buggy server implementations who expects such.
.TP
\fB--sink-address \fI\fIaddress
\fP\fP
Initial address receiving the demurrage value withdrawal.
.TP
\fB-u\fP, \fB--unsafe\fP
Allow addresses that do not pass checksum.
.TP
\fB-v\fP
Verbose. Show logs for important state changes.
.TP
\fB-vv\fP
Very verbose. Show logs with debugging information.
.TP
\fB-w\fP
Wait for the last transaction to be confirmed on the network. Will generate an error if the EVM execution fails.
.TP
\fB-ww\fP
Wait for \fIall\fP transactions sequentially to be confirmed on the network. Will generate an error if EVM execution fails for any of the transactions.
.TP
\fB-y \fI\fIpath\fP\fP, \fB--key-path \fI\fIpath\fP\fP
Path to signing key. Overrides the \fIWALLET_KEY_FILE\fP configuration setting.
.SH CONFIGURATION
All configuration settings may be overriden both by environment variables, or by overriding settings with the contents of ini-files in the directory defined by the \fB-c\fP option.
The active configuration, with values assigned from environment and arguments, can be output using the \fB--dumpconfig\fP \fIformat\fP option. Note that entries having keys prefixed with underscore (e.g. _SEQ) are not actual configuration settings, and thus cannot be overridden with environment variables.
To refer to a configuration setting by environment variables, the \fIsection\fP and \fIkey\fP are concatenated together with an underscore, and transformed to upper-case. For example, the configuration variable \fIFOO_BAZ_BAR\fP refers to an ini-file entry as follows:
.EX
[foo]
bar_baz = xyzzy
.EE
In the \fBENVIRONMENT\fP section below, the relevant configuration settings for this tool is listed along with a short description of its meaning.
Some configuration settings may also be overriden by command line options. Also note that the use of the \fB-n\fP and \fB--env-prefix\fP options affect how environment and configuration is read. The effects of options on how configuration settings are affective is described in the respective \fBOPTIONS\fP section.
.SH SPECIFYING DEMURRAGE
Demurrage is specified as a parts-per-million value in terms of the chosen \fI--redistribution-period\fP.
For example, a value of \fB20000\fP with a \fI--redistribution-period\fP of \fB43200\fP corresponds to a \fB2%\fP demurrage per \fB30\fP days.
.SH ENVIRONMENT
.TP
\fICHAIN_SPEC\fP
String specifying the type of chain connected to, in the format \fI<engine>:<fork>:<network_id>:<common_name>\fP. For EVM nodes the \fIengine\fP value will always be \fIevm\fP.
.TP
\fIRPC_AUTH\fP
Authentication method to use for the \fIRPC_PROVIDER\fP. Currently only \fIbasic\fP is supported.
.TP
\fIRPC_CREDENTIALS\fP
Authentication credentials to use for \fIRPC_AUTH\fP. For \fIbasic\fP authentication the value must be given as \fI<user>:<pass>\fP.
.TP
\fIRPC_DIALECT\fP
Enables translations of EVM node specific formatting and response codes.
.TP
\fIRPC_PROVIDER\fP
Fully-qualified URL to the RPC endpoint of the blockchain node.
.TP
\fIWALLET_KEY_FILE\fP
The wallet key file containing private key to use for transaction signing. Overridden by \fB-y\fP.
.TP
\fIWALLET_PASSPHRASE\fP
Passphrase to unlock wallet. \fBWARNING:\fP it is \fBunsafe\fP to pass the passphrase as an environment variable. If the key unlocks something of value, the passphrase should rather be in a configuration file, preferably as an encrypted entry. Alternatively, a passphrase can be read from file using the \fB--passphrase-file\fP option. Files containing passphrases should only be accessible by the owner.
.SH LICENSE
This documentation and its source is licensed under the Creative Commons Attribution-Sharealike 4.0 International license.
The source code of the tool this documentation describes is licensed under the GNU General Public License 3.0.
.SH COPYRIGHT
Louis Holbrook <dev@holbrook.no> (https://holbrook.no)
PGP: 59A844A484AC11253D3A3E9DCDCBD24DD1D0E001
.SH SOURCE CODE
https://git.defalsify.org
.SH SEE ALSO
.BP
confini-dump(1), eth-keyfile(1)

View File

@ -1,5 +0,0 @@
.SH SPECIFYING DEMURRAGE
Demurrage is specified as a parts-per-million value in terms of the chosen \fI--redistribution-period\fP.
For example, a value of \fB20000\fP with a \fI--redistribution-period\fP of \fB43200\fP corresponds to a \fB2%\fP demurrage per \fB30\fP days.

View File

@ -1,22 +0,0 @@
.TH erc20-demurrage-token-publish 1
.SH NAME
erc20-demurrage-token-publish \- Publishing tool for the ERC20 Demurrage Token smart contract.
.SH SYNOPSIS
.SY erc20-demurrage-token-publish
.RI --name NAME --symbol SYMBOL --decimals DECIMALS --demurrage-level PPM --redistribution-period MINUTES --sink-address ADDRESS
.YS
.SH DESCRIPTION
.P
This tool generates the appropriate bytecode to store the ERC20 Demurrage Token smartcontract code on an EVM chain, along with the required construction parameters.
Arguments for EVM encoding and interaction with EVM RPC node is handled by the \fBchainlib-eth\fP python package.
Specific arguments for this tool are the \fB--demurrage-level\fP, \fB--redistribution-period\fP and \fB--sink-address\fP arguments.
.SS OPTIONS

View File

@ -1,3 +0,0 @@
demurragelevel Level of decay per minute. See SPECIFYING DEMURRAGE below. --demurrage-level level
redistributionperiod Number of minuntes between each withdrawal of demurraged value is possible. --redistribution-period minutes
sinkaddress Initial address receiving the demurrage value withdrawal. --sink-address address

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
name = erc20-demurrage-token name = erc20-demurrage-token
version = 0.5.6 version = 0.5.1
description = ERC20 token with redistributed continual demurrage description = ERC20 token with redistributed continual demurrage
author = Louis Holbrook author = Louis Holbrook
author_email = dev@holbrook.no author_email = dev@holbrook.no

View File

@ -1,5 +1,4 @@
from setuptools import setup from setuptools import setup
import os
requirements = [] requirements = []
f = open('requirements.txt', 'r') f = open('requirements.txt', 'r')
@ -20,7 +19,6 @@ while True:
f.close() f.close()
man_dir = 'man/build'
setup( setup(
package_data={ package_data={
'': [ '': [
@ -30,8 +28,4 @@ setup(
include_package_data=True, include_package_data=True,
install_requires=requirements, install_requires=requirements,
tests_require=test_requirements, tests_require=test_requirements,
data_files=[("man/man1", [
os.path.join(man_dir, 'erc20-demurrage-token-publish.1'),
]
)],
) )

View File

@ -308,30 +308,6 @@ class TestBasic(TestDemurrageDefault):
self.assertEqual(r['status'], 1) self.assertEqual(r['status'], 1)
def test_approve_max(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.approve(self.address, self.accounts[0], self.accounts[1], int.from_bytes(b'\xff' * 32, byteorder='big'))
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 0)
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 + (60 * 60 * 24 * 365 * 10))
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], int.from_bytes(b'\xff' * 32, byteorder='big'))
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
def test_transfer_from(self): def test_transfer_from(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)

View File

@ -6,8 +6,6 @@ import "aux/ABDKMath64x64.sol";
contract DemurrageTokenSingleNocap { contract DemurrageTokenSingleNocap {
uint256 constant VALUE_LIMIT = 1 << 63;
struct redistributionItem { struct redistributionItem {
uint32 period; uint32 period;
uint72 value; uint72 value;
@ -75,7 +73,7 @@ contract DemurrageTokenSingleNocap {
mapping (address => bool) minter; mapping (address => bool) minter;
// Storage for ERC20 approve/transferFrom methods // Storage for ERC20 approve/transferFrom methods
mapping (address => mapping (address => uint256 ) ) public allowance; // holder -> spender -> amount (amount is subject to demurrage) mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
// Address to send unallocated redistribution tokens // Address to send unallocated redistribution tokens
address public sinkAddress; address public sinkAddress;
@ -118,9 +116,6 @@ contract DemurrageTokenSingleNocap {
// Implements Expire // Implements Expire
event Expired(uint256 _timestamp); event Expired(uint256 _timestamp);
// Implements Expire
event ExpiryChange(uint256 indexed _oldTimestamp, uint256 _newTimestamp);
event Cap(uint256 indexed _oldCap, uint256 _newCap); event Cap(uint256 indexed _oldCap, uint256 _newCap);
// Implements Seal // Implements Seal
@ -185,16 +180,13 @@ contract DemurrageTokenSingleNocap {
// Cannot be set to a time in the past. // Cannot be set to a time in the past.
function setExpirePeriod(uint256 _expirePeriod) public { function setExpirePeriod(uint256 _expirePeriod) public {
uint256 r; uint256 r;
uint256 oldTimestamp;
require(!isSealed(EXPIRY_STATE)); require(!isSealed(EXPIRY_STATE));
require(!expired); require(!expired);
require(msg.sender == owner); require(msg.sender == owner);
r = periodStart + (_expirePeriod * periodDuration); r = periodStart + (_expirePeriod * periodDuration);
require(r > expires); require(r > expires);
oldTimestamp = expires;
expires = r; expires = r;
emit ExpiryChange(oldTimestamp, expires);
} }
// Change max token supply. // Change max token supply.
@ -217,7 +209,6 @@ contract DemurrageTokenSingleNocap {
// Expire the contract if expire is set and we have gone over the threshold. // Expire the contract if expire is set and we have gone over the threshold.
// Finalizes demurrage up to the timestamp of the expiry. // 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. // 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.
// Implements Expire
function applyExpiry() public returns(uint8) { function applyExpiry() public returns(uint8) {
if (expired) { if (expired) {
return 1; return 1;
@ -255,7 +246,7 @@ contract DemurrageTokenSingleNocap {
// Implements Writer // Implements Writer
function isWriter(address _minter) public view returns(bool) { function isWriter(address _minter) public view returns(bool) {
return minter[_minter] || _minter == owner; return minter[_minter];
} }
/// Implements ERC20 /// Implements ERC20
@ -317,7 +308,6 @@ contract DemurrageTokenSingleNocap {
v = account[msg.sender]; v = account[msg.sender];
account[msg.sender] = 0; account[msg.sender] = 0;
account[_account] += v; account[_account] += v;
emit Transfer(msg.sender, _account, v);
return v; return v;
} }
@ -599,14 +589,7 @@ contract DemurrageTokenSingleNocap {
changePeriod(); changePeriod();
// dex code will attempt uint256max approve, but contract cannot handle that size
// truncate to biggest possible value
if (_value <= VALUE_LIMIT) {
baseValue = toBaseAmount(_value); baseValue = toBaseAmount(_value);
} else {
baseValue = VALUE_LIMIT;
}
allowance[msg.sender][_spender] = baseValue; allowance[msg.sender][_spender] = baseValue;
emit Approval(msg.sender, _spender, _value); emit Approval(msg.sender, _spender, _value);
return true; return true;
@ -768,7 +751,7 @@ contract DemurrageTokenSingleNocap {
if (_sum == 0xabe1f1f5) { // Writer if (_sum == 0xabe1f1f5) { // Writer
return true; return true;
} }
if (_sum == 0x841a0e94) { // Expire if (_sum == 0xcb52c823) { // Expire
return true; return true;
} }
if (_sum == 0x01ffc9a7) { // ERC165 if (_sum == 0x01ffc9a7) { // ERC165