erc20-demurrage-token/README.md
2023-08-14 14:10:09 +01:00

406 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`
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)
- [Seal](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Seal.sol)
- [Writer](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Writer.sol)
## Dependencies
The token contract uses the
[ADBKMath](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol)
library to calculate exponentials.
## Permissions
The smart contract defines three levels of access.
1. Voucher contract owner
2. Voucher minter
3. Voucher holder
### 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 **ERC173**
standard interface.
### 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 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.
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.