Compare commits

...

10 Commits

Author SHA1 Message Date
lash a40385a5a0
Truncate approve request out of value bounds 2023-10-05 17:41:52 +01:00
lash d7051b26c0
Make allowance method public 2023-09-05 03:22:33 +01:00
lash 14fb2fe8ea
Transfer event 2023-08-14 14:10:09 +01:00
lash 6d7792110d
Package man page with python 2023-05-18 06:42:14 +01:00
lash 76e8170014
Add makefiles, update readme 2023-05-16 11:21:38 +01:00
lash 091967bafd
WIP texinfo docs, elaborate on tools, supply cap 2023-05-15 15:22:59 +01:00
lash 0ed97b36b0
WIP texinfo documentation as improvement over existing readme 2023-05-14 21:07:11 +01:00
lash 8242d3a62f
Add ExpiryChange event 2023-03-26 08:03:44 +01:00
lash f2648dfb68
Update Expire ERC165 signature 2023-03-26 07:34:27 +01:00
lash d4906529a3
Update interfaces, add ERC5679 aliases 2023-03-26 07:26:02 +01:00
23 changed files with 1155 additions and 116 deletions

4
CAVEAT
View File

@ -1,5 +1 @@
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.

10
Makefile Normal file
View File

@ -0,0 +1,10 @@
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,131 +1,405 @@
# RedistributedDemurrageToken
# Overview
## 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, its 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.
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.
## Ownership
`Sink Account`
The intermediate beneficiary of the demurraged amount, which may or may
not redistribute value.
* Contract creator is owner
* Ownership can be transferred
`Base balance`
The inflated balance of each used which is stored for bookkeeping.
# Use Case
## Mint
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*.
* 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.
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.
## Input parameters
Introducing demurrage on this vehicle discourages *withholding* the
voucher, for example for speculative advantage.
The redistrbution period is passed to the contract in minutes. E.g. a redistribution period of one month would be approximately 43200 minutes.
This also encourages increased *velocity* of voucher use.
The demurrage level specified as the percentage of continuous growth per minute:
## Example
`(1 - percentage) ^ (1 / period)`
Given:
E.g. A demurrage of 2% monthly would be defined as:
- 10 voucher holders.
`(1 - 0.02) ^ (1 / 43200) ~ 0.99999953234484737109`
- A total supply of 1000 tokens.
The number must be provided to the contract as a 64x64 bit fixed-point number (where the integer part is 0).
- Demurrage of 2% per 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.
- 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.
## Demurrage calculation
The Sink Address will have a balance of 20 vouchers after the same
period.
The demurrage calculation inside the contract is done by the following formula, where `demurrageLevel` is the demurrage level input parameter of the contract:
Note that after the redistribution the total of all balances will equal
the total minted amount.
`newDemurrageModifier = currentDemurrageModifier * (e ^ (ln(demurrageLevel) * minutes))`
Note that all accounts holding such vouchers are effected by demurrage
(even the Sink Account, pending redistribution).
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.
# Smart contract
## Common interfaces
All client-facing values (_balance output_ , _transfer inputs_) are adjusted with `demurrageModifier`.
The smart contract is written in solidity, compatible with 0.8.x.
e.g. `_balance output_ = user_balance - user_balance * demurrageModifier`
It implements a number of interfaces both from the Ethereum (ERC)
standards aswell as the Community Inclusion Currency contract interface
suite.
### ERC standard interfaces
## Redistribution
- [ERC20 - Token Standard](https://eips.ethereum.org/EIPS/eip-20)
* 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.
- [ERC165 - Standard Interface
Detection](https://eips.ethereum.org/EIPS/eip-165)
- [ERC173 - Contract Ownership
Standard](https://eips.ethereum.org/EIPS/eip-173)
## Data representation
- [ERC5679 - Token Minting and Burning (as part of CIC.Minter and
CIC.Burner)](https://eips.ethereum.org/EIPS/eip-5679)
Token parameters are truncated when calculating demurrage and redistribution:
### CIC interfaces
* Redistribution period: 32 bits
* Token supply: 72 bits
* Demurrage modifier: 64 bits
- [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)
## Expiration
- [Minter](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Minter.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.
- [Seal](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Seal.sol)
Expiration may be set in terms of redistribution periods.
- [Writer](https://git.grassecon.net/cicnet/cic-contracts/src/branch/master/solidity/Writer.sol)
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.
## Dependencies
The token contract uses the
[ADBKMath](https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.sol)
library to calculate exponentials.
## Supply
## Permissions
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.
The smart contract defines three levels of access.
1. Voucher contract owner
## Mutability
2. Voucher minter
The following parameters may not be changed after contract is published:
3. Voucher holder
* Demurrage level
* Redistribution period
### Contract owner
The contract provides a sealing feature which prohibits further changes to parameters that can initially be edited. These include:
When the contract is published to the network, the signer account of the
publishing transaction will be the contract owner.
* Adding and removing writers (addresses that may mint tokens)
* Sink addres
* Expiry period
* Supply limit
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
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.
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.
## QA
# Tools
* Tests are implemented using the `chaintool` python package suite.
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.
## Known issues
`--demurrage-level`
The percentage of demurrage in terms of the redistribution period,
defined as parts-per-million.
* A `transferFrom` following an `approve` call, when called across period thresholds, may fail if margin to demurraged amount is insufficient.
`--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.

4
doc/texinfo/Makefile Normal file
View File

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

221
doc/texinfo/contract.texi Normal file
View File

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

29
doc/texinfo/index.texi Normal file
View File

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

47
doc/texinfo/overview.texi Normal file
View File

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

57
doc/texinfo/tools.texi Normal file
View File

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

32
doc/texinfo/usecase.texi Normal file
View File

@ -0,0 +1,32 @@
@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,3 +1,17 @@
- 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
* Correct burn interface implementation
- 0.4.1

View File

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

8
python/Makefile Normal file
View File

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

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

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

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

@ -0,0 +1,3 @@
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]
name = erc20-demurrage-token
version = 0.5.0
version = 0.5.6
description = ERC20 token with redistributed continual demurrage
author = Louis Holbrook
author_email = dev@holbrook.no

View File

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

View File

@ -308,6 +308,30 @@ class TestBasic(TestDemurrageDefault):
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):
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = DemurrageToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)

View File

@ -1,17 +1,19 @@
pragma solidity >= 0.8.0;
import "aux/ABDKMath64x64.sol";
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-License-Identifier: AGPL-3.0-or-later
contract DemurrageTokenSingleNocap {
uint256 constant VALUE_LIMIT = 1 << 63;
struct redistributionItem {
uint32 period;
uint72 value;
uint64 demurrage;
}
redistributionItem[] public redistributions; // uint51(unused) | uint64(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
redistributionItem[] public redistributions;
// Account balances
mapping (address => uint256) account;
@ -23,7 +25,7 @@ contract DemurrageTokenSingleNocap {
// Cached demurrage timestamp; the timestamp for which demurrageAmount was last calculated
uint256 public demurrageTimestamp;
// Implements EIP172
// Implements EIP173
address public owner;
address newOwner;
@ -37,8 +39,6 @@ contract DemurrageTokenSingleNocap {
// Implements ERC20
uint256 public immutable decimals;
// Implements ERC20
//uint256 public totalSupply;
uint256 supply;
// Last executed period
@ -48,7 +48,7 @@ contract DemurrageTokenSingleNocap {
uint256 public totalSink;
// Value of burnt tokens (burnt tokens do not decay)
uint256 public burned;
uint256 burned;
// 128 bit resolution of the demurrage divisor
// (this constant x 1000000 is contained within 128 bits)
@ -75,7 +75,7 @@ contract DemurrageTokenSingleNocap {
mapping (address => bool) minter;
// Storage for ERC20 approve/transferFrom methods
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
mapping (address => mapping (address => uint256 ) ) public allowance; // holder -> spender -> amount (amount is subject to demurrage)
// Address to send unallocated redistribution tokens
address public sinkAddress;
@ -93,7 +93,7 @@ contract DemurrageTokenSingleNocap {
// Implements ERC20
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
// New tokens minted
// Implements Minter
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
// New demurrage cache milestone calculated
@ -109,23 +109,30 @@ contract DemurrageTokenSingleNocap {
//event Debug(bytes32 _foo);
event Debug(int128 indexed _foo, uint256 indexed _bar);
// Emitted when tokens are burned
// Implements Burn
event Burn(address indexed _burner, uint256 _value);
// EIP173
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
// Implements Expire
event Expired(uint256 _timestamp);
// Implements Expire
event ExpiryChange(uint256 indexed _oldTimestamp, uint256 _newTimestamp);
event Cap(uint256 indexed _oldCap, uint256 _newCap);
// Implements Sealer
// Implements Seal
uint256 public sealState;
uint8 constant WRITER_STATE = 1;
uint8 constant SINK_STATE = 2;
uint8 constant EXPIRY_STATE = 4;
uint8 constant CAP_STATE = 8;
// Implements Seal
uint256 constant public maxSealState = 15;
// Implements Seal
event SealStateChange(bool indexed _final, uint256 _sealState);
@ -157,7 +164,6 @@ contract DemurrageTokenSingleNocap {
sinkAddress = _defaultSinkAddress;
}
// Implements Sealer
function seal(uint256 _state) public returns(uint256) {
require(_state < 16, 'ERR_INVALID_STATE');
require(_state & sealState == 0, 'ERR_ALREADY_LOCKED');
@ -166,7 +172,6 @@ contract DemurrageTokenSingleNocap {
return uint256(sealState);
}
// Implements Sealer
function isSealed(uint256 _state) public view returns(bool) {
require(_state < maxSealState);
if (_state == 0) {
@ -175,17 +180,25 @@ contract DemurrageTokenSingleNocap {
return _state & sealState == _state;
}
// Set when token expires.
// Value is set it terms of redistribution periods.
// Cannot be set to a time in the past.
function setExpirePeriod(uint256 _expirePeriod) public {
uint256 r;
uint256 oldTimestamp;
require(!isSealed(EXPIRY_STATE));
require(!expired);
require(msg.sender == owner);
r = periodStart + (_expirePeriod * periodDuration);
require(r > expires);
oldTimestamp = expires;
expires = r;
emit ExpiryChange(oldTimestamp, expires);
}
// Change max token supply.
// Can only increase supply cap, not decrease.
function setMaxSupply(uint256 _cap) public {
require(!isSealed(CAP_STATE));
require(msg.sender == owner);
@ -204,6 +217,7 @@ contract DemurrageTokenSingleNocap {
// 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.
// Implements Expire
function applyExpiry() public returns(uint8) {
if (expired) {
return 1;
@ -222,6 +236,7 @@ contract DemurrageTokenSingleNocap {
}
// Given address will be allowed to call the mintTo() function
// Implements Writer
function addWriter(address _minter) public returns (bool) {
require(!isSealed(WRITER_STATE));
require(msg.sender == owner);
@ -230,6 +245,7 @@ contract DemurrageTokenSingleNocap {
}
// Given address will no longer be allowed to call the mintTo() function
// Implements Writer
function deleteWriter(address _minter) public returns (bool) {
require(!isSealed(WRITER_STATE));
require(msg.sender == owner || _minter == msg.sender);
@ -237,6 +253,11 @@ contract DemurrageTokenSingleNocap {
return true;
}
// Implements Writer
function isWriter(address _minter) public view returns(bool) {
return minter[_minter] || _minter == owner;
}
/// Implements ERC20
function balanceOf(address _account) public view returns (uint256) {
int128 baseBalance;
@ -296,12 +317,14 @@ contract DemurrageTokenSingleNocap {
v = account[msg.sender];
account[msg.sender] = 0;
account[_account] += v;
emit Transfer(msg.sender, _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) {
// Implements Minter
function mintTo(address _beneficiary, uint256 _amount) public returns (bool) {
uint256 baseAmount;
require(applyExpiry() == 0);
@ -320,6 +343,18 @@ contract DemurrageTokenSingleNocap {
return true;
}
// Implements Minter
function mint(address _beneficiary, uint256 _amount, bytes calldata _data) public {
_data;
mintTo(_beneficiary, _amount);
}
// Implements Minter
function safeMint(address _beneficiary, uint256 _amount, bytes calldata _data) public {
_data;
mintTo(_beneficiary, _amount);
}
// Deserializes the redistribution word
function toRedistribution(uint256 _participants, int128 _demurrageModifier, uint256 _value, uint256 _period) public pure returns(redistributionItem memory) {
redistributionItem memory redistribution;
@ -508,6 +543,7 @@ contract DemurrageTokenSingleNocap {
return (block.timestamp - _target) / 60;
}
// Equality check for empty redistribution data
function isEmptyRedistribution(redistributionItem memory _redistribution) public pure returns(bool) {
if (_redistribution.period > 0) {
return false;
@ -545,7 +581,8 @@ contract DemurrageTokenSingleNocap {
return ABDKMath64x64.toUInt(r);
}
// Implements ERC20, triggers tax and/or redistribution
// Triggers tax and/or redistribution
// Implements ERC20
function approve(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
uint8 ex;
@ -562,7 +599,14 @@ contract DemurrageTokenSingleNocap {
changePeriod();
baseValue = toBaseAmount(_value);
// dex code will attempt uint256max approve, but contract cannot handle that size
// truncate to biggest possible value
if (_value <= VALUE_LIMIT) {
baseValue = toBaseAmount(_value);
} else {
baseValue = VALUE_LIMIT;
}
allowance[msg.sender][_spender] = baseValue;
emit Approval(msg.sender, _spender, _value);
return true;
@ -595,7 +639,8 @@ contract DemurrageTokenSingleNocap {
return true;
}
// Implements ERC20, triggers tax and/or redistribution
// Triggers tax and/or redistribution
// Implements ERC20
function transfer(address _to, uint256 _value) public returns (bool) {
uint256 baseValue;
bool result;
@ -615,7 +660,8 @@ contract DemurrageTokenSingleNocap {
return result;
}
// Implements ERC20, triggers tax and/or redistribution
// Triggers tax and/or redistribution
// Implements ERC20
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
uint256 baseValue;
bool result;
@ -649,26 +695,20 @@ contract DemurrageTokenSingleNocap {
// Implements EIP173
function transferOwnership(address _newOwner) public returns (bool) {
require(msg.sender == owner);
newOwner = _newOwner;
return true;
}
// Implements OwnedAccepter
function acceptOwnership() public returns (bool) {
address oldOwner;
require(msg.sender == newOwner);
oldOwner = owner;
owner = newOwner;
newOwner = address(0);
require(msg.sender == owner);
oldOwner = owner;
owner = _newOwner;
emit OwnershipTransferred(oldOwner, owner);
return true;
}
// Explicitly and irretrievably burn tokens
// Only token minters can burn tokens
function burn(uint256 _value) public returns (bool) {
// Implements Burner
function burn(uint256 _value) public returns(bool) {
require(applyExpiry() == 0);
require(minter[msg.sender] || msg.sender == owner, 'ERR_ACCESS');
require(_value <= account[msg.sender]);
@ -681,17 +721,31 @@ contract DemurrageTokenSingleNocap {
return true;
}
// Implements Burner
function burn(address _from, uint256 _value, bytes calldata _data) public {
require(_from == msg.sender, 'ERR_ONLY_SELF_BURN');
_data;
burn(_value);
}
// Implements Burner
function burn() public returns(bool) {
return burn(account[msg.sender]);
}
// Implements ERC20
function totalSupply() public view returns (uint256) {
return supply - burned;
}
// Return total number of burned tokens
// Implements Burner
function totalBurned() public view returns (uint256) {
return burned;
}
// Return total number of tokens ever minted
// Implements Burner
function totalMinted() public view returns (uint256) {
return supply;
}
@ -699,16 +753,31 @@ contract DemurrageTokenSingleNocap {
// Implements EIP165
function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20
if (_sum == 0xb61bc941) { // ERC20
return true;
}
if (_sum == 0x449a52f8) { // Minter
if (_sum == 0x5878bcf4) { // Minter
return true;
}
if (_sum == 0x01ffc9a7) { // EIP165
if (_sum == 0xbc4babdd) { // Burner
return true;
}
if (_sum == 0x9493f8b2) { // EIP173
if (_sum == 0x0d7491f8) { // Seal
return true;
}
if (_sum == 0xabe1f1f5) { // Writer
return true;
}
if (_sum == 0x841a0e94) { // Expire
return true;
}
if (_sum == 0x01ffc9a7) { // ERC165
return true;
}
if (_sum == 0x9493f8b2) { // ERC173
return true;
}
if (_sum == 0xd0017968) { // ERC5678Ext20
return true;
}
return false;