2021-02-02 11:26:44 +01:00
pragma solidity > 0 . 6 . 11 ;
// SPDX-License-Identifier: GPL-3.0-or-later
2021-02-05 08:45:44 +01:00
// TODO: assign bitmask values to contants
2021-02-02 11:26:44 +01:00
contract RedistributedDemurrageToken {
address public owner ;
string public name ;
string public symbol ;
2021-02-05 18:52:01 +01:00
uint256 public decimals ;
2021-02-02 11:26:44 +01:00
uint256 public totalSupply ;
uint256 public periodStart ;
uint256 public periodDuration ;
2021-02-02 16:10:21 +01:00
uint256 public taxLevel ; // PPM
uint256 public demurrageModifier ; // PPM
2021-02-02 11:26:44 +01:00
2021-02-05 12:32:17 +01:00
bytes32 [ ] public redistributions ; // uint1(isFractional) | uint1(unused) | uint38(participants) | uint160(value) | uint56(period)
2021-02-05 09:22:36 +01:00
mapping ( address => bytes32 ) account ; // uint20(unused) | uint56(period) | uint160(value)
2021-02-02 16:10:21 +01:00
mapping ( address => bool ) minter ;
2021-02-05 09:22:36 +01:00
mapping ( address => mapping ( address => uint256 ) ) allowance ; // holder -> spender -> amount (amount is subject to demurrage)
2021-02-02 11:26:44 +01:00
2021-02-05 09:44:15 +01:00
address sinkAddress ; // receives redistribuion remainders
2021-02-02 11:26:44 +01:00
event Transfer ( address indexed _from , address indexed _to , uint256 _value ) ;
event Approval ( address indexed _owner , address indexed _spender , uint256 _value ) ;
2021-02-03 09:00:24 +01:00
event Mint ( address indexed _minter , address indexed _beneficiary , uint256 _value ) ;
//event Debug(uint256 _foo);
2021-02-05 12:02:05 +01:00
event Taxed ( uint256 indexed _period , uint256 remainder ) ;
2021-02-03 09:00:24 +01:00
event Redistribution ( address indexed _account , uint256 indexed _period , uint256 _value ) ;
2021-02-02 11:26:44 +01:00
2021-02-05 12:02:05 +01:00
constructor ( string memory _name , string memory _symbol , uint8 _decimals , uint32 _taxLevel , uint256 _period , address _defaultSinkAddress ) public {
2021-02-02 11:26:44 +01:00
owner = msg . sender ;
2021-02-02 16:10:21 +01:00
minter [ owner ] = true ;
2021-02-02 11:26:44 +01:00
periodStart = block . number ;
periodDuration = _period ;
taxLevel = _taxLevel ;
name = _name ;
symbol = _symbol ;
2021-02-05 09:22:36 +01:00
decimals = _decimals ;
2021-02-02 16:10:21 +01:00
demurrageModifier = 1000000 ;
2021-02-05 12:56:57 +01:00
sinkAddress = _defaultSinkAddress ;
2021-02-02 17:53:35 +01:00
bytes32 initialRedistribution = toRedistribution ( 0 , 0 , 1 ) ;
2021-02-02 11:26:44 +01:00
redistributions . push ( initialRedistribution ) ;
}
2021-02-05 12:02:05 +01:00
// Given address will be allowed to call the mintTo() function
2021-02-02 16:10:21 +01:00
function addMinter ( address _minter ) public returns ( bool ) {
require ( msg . sender == owner ) ;
minter [ _minter ] = true ;
return true ;
}
2021-02-03 09:16:01 +01:00
/// ERC20
2021-02-02 16:10:21 +01:00
function balanceOf ( address _account ) public view returns ( uint256 ) {
uint256 baseBalance = getBaseBalance ( _account ) ;
uint256 inverseModifier = 1000000 - demurrageModifier ;
uint256 balanceModifier = ( inverseModifier * baseBalance ) / 1000000 ;
return baseBalance - balanceModifier ;
}
2021-02-03 09:16:01 +01:00
/// Balance unmodified by demurrage
2021-02-02 16:10:21 +01:00
function getBaseBalance ( address _account ) private view returns ( uint256 ) {
return uint256 ( account [ _account ] ) & 0x00ffffffffffffffffffffffffffffffffffffffff ;
}
2021-02-03 09:16:01 +01:00
/// Increases base balance for a single account
2021-02-05 12:56:57 +01:00
function increaseBaseBalance ( address _account , uint256 _delta ) private returns ( bool ) {
2021-02-02 21:14:41 +01:00
uint256 oldBalance ;
uint256 newBalance ;
oldBalance = getBaseBalance ( _account ) ;
newBalance = oldBalance + _delta ;
2021-02-05 12:56:57 +01:00
require ( uint160 ( newBalance ) > uint160 ( oldBalance ) , ' ERR_WOULDWRAP ' ) ; // revert if increase would result in a wrapped value
2021-02-02 21:14:41 +01:00
account [ _account ] &= bytes32 ( 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 ) ;
account [ _account ] |= bytes32 ( newBalance & 0x00ffffffffffffffffffffffffffffffffffffffff ) ;
2021-02-02 16:10:21 +01:00
return true ;
}
2021-02-03 09:16:01 +01:00
/// Decreases base balance for a single account
2021-02-05 12:56:57 +01:00
function decreaseBaseBalance ( address _account , uint256 _delta ) private returns ( bool ) {
2021-02-02 21:14:41 +01:00
uint256 oldBalance ;
uint256 newBalance ;
oldBalance = getBaseBalance ( _account ) ;
2021-02-05 12:56:57 +01:00
require ( oldBalance >= _delta , ' ERR_OVERSPEND ' ) ; // overspend guard
2021-02-02 21:14:41 +01:00
newBalance = oldBalance - _delta ;
account [ _account ] &= bytes32 ( 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 ) ;
account [ _account ] |= bytes32 ( newBalance & 0x00ffffffffffffffffffffffffffffffffffffffff ) ;
2021-02-02 16:10:21 +01:00
return true ;
}
2021-02-03 09:16:01 +01:00
// Creates new tokens out of thin air, and allocates them to the given address
// Triggers tax
2021-02-02 16:10:21 +01:00
function mintTo ( address _beneficiary , uint256 _amount ) external returns ( bool ) {
require ( minter [ msg . sender ] ) ;
2021-02-02 19:09:13 +01:00
// TODO: get base amount for minting
applyTax ( ) ;
2021-02-02 17:53:35 +01:00
totalSupply += _amount ;
2021-02-05 12:56:57 +01:00
increaseBaseBalance ( _beneficiary , _amount ) ;
2021-02-02 16:10:21 +01:00
emit Mint ( msg . sender , _beneficiary , _amount ) ;
2021-02-02 17:53:35 +01:00
saveRedistributionSupply ( ) ;
2021-02-02 16:10:21 +01:00
return true ;
}
2021-02-03 09:16:01 +01:00
// Deserializes the redistribution word
2021-02-02 11:26:44 +01:00
function toRedistribution ( uint256 _participants , uint256 _value , uint256 _period ) private pure returns ( bytes32 ) {
bytes32 redistribution ;
2021-02-02 17:53:35 +01:00
2021-02-05 12:32:17 +01:00
redistribution |= bytes32 ( ( _participants & 0x7fffffffff ) << 216 ) ;
2021-02-02 17:53:35 +01:00
redistribution |= bytes32 ( ( _value & 0xffffffffffffffffffffffff ) << 56 ) ;
redistribution |= bytes32 ( _period & 0xffffffffffffff ) ;
2021-02-02 11:26:44 +01:00
return redistribution ;
}
2021-02-03 09:16:01 +01:00
// Serializes the demurrage period part of the redistribution word
2021-02-02 11:26:44 +01:00
function toRedistributionPeriod ( bytes32 redistribution ) public pure returns ( uint256 ) {
2021-02-02 19:09:13 +01:00
return uint256 ( redistribution & 0x00000000000000000000000000000000000000000000000000ffffffffffffff ) ;
2021-02-02 11:26:44 +01:00
}
2021-02-03 09:16:01 +01:00
// Serializes the supply part of the redistribution word
2021-02-02 21:14:41 +01:00
function toRedistributionSupply ( bytes32 redistribution ) public pure returns ( uint256 ) {
return uint256 ( redistribution & 0x0000000000ffffffffffffffffffffffffffffffffffffffff00000000000000 ) >> 56 ;
}
2021-02-03 09:16:01 +01:00
// Serializes the number of participants part of the redistribution word
2021-02-02 21:14:41 +01:00
function toRedistributionParticipants ( bytes32 redistribution ) public pure returns ( uint256 ) {
2021-02-05 12:32:17 +01:00
return uint256 ( redistribution & 0x7fffffffff000000000000000000000000000000000000000000000000000000 ) >> 216 ;
2021-02-02 21:14:41 +01:00
}
2021-02-03 09:16:01 +01:00
// Client accessor to the redistributions array length
2021-02-02 11:26:44 +01:00
function redistributionCount ( ) public view returns ( uint256 ) {
return redistributions . length ;
}
2021-02-03 09:16:01 +01:00
// Add number of participants for the current redistribution period by one
2021-02-02 17:53:35 +01:00
function incrementRedistributionParticipants ( ) private returns ( bool ) {
uint256 currentRedistribution ;
uint256 participants ;
currentRedistribution = uint256 ( redistributions [ redistributions . length - 1 ] ) ;
2021-02-05 12:32:17 +01:00
participants = ( ( currentRedistribution & 0x7fffffffff000000000000000000000000000000000000000000000000000000 ) >> 216 ) + 1 ;
currentRedistribution &= 0x8000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff ;
2021-02-02 17:53:35 +01:00
currentRedistribution |= participants << 216 ;
2021-02-03 09:00:24 +01:00
//emit Debug(participants);
2021-02-02 17:53:35 +01:00
redistributions [ redistributions . length - 1 ] = bytes32 ( currentRedistribution ) ;
}
2021-02-03 09:16:01 +01:00
// Save the current total supply amount to the current redistribution period
2021-02-02 17:53:35 +01:00
function saveRedistributionSupply ( ) private returns ( bool ) {
uint256 currentRedistribution ;
currentRedistribution = uint256 ( redistributions [ redistributions . length - 1 ] ) ;
2021-02-05 12:32:17 +01:00
currentRedistribution &= 0xffffffffff0000000000000000000000000000000000000000ffffffffffffff ;
2021-02-02 19:09:13 +01:00
currentRedistribution |= totalSupply << 56 ;
2021-02-02 17:53:35 +01:00
redistributions [ redistributions . length - 1 ] = bytes32 ( currentRedistribution ) ;
}
2021-02-03 09:16:01 +01:00
// Get the demurrage period of the current block number
2021-02-02 11:26:44 +01:00
function actualPeriod ( ) public view returns ( uint256 ) {
2021-02-02 19:09:13 +01:00
return ( block . number - periodStart ) / periodDuration + 1 ;
2021-02-02 11:26:44 +01:00
}
2021-02-03 09:16:01 +01:00
// Add an entered demurrage period to the redistribution array
2021-02-02 11:26:44 +01:00
function checkPeriod ( ) private view returns ( bytes32 ) {
2021-02-02 21:14:41 +01:00
bytes32 lastRedistribution ;
uint256 currentPeriod ;
lastRedistribution = redistributions [ redistributions . length - 1 ] ;
currentPeriod = this . actualPeriod ( ) ;
2021-02-02 19:09:13 +01:00
if ( currentPeriod <= toRedistributionPeriod ( lastRedistribution ) ) {
2021-02-02 11:26:44 +01:00
return bytes32 ( 0x00 ) ;
}
return lastRedistribution ;
}
2021-02-03 09:16:01 +01:00
// Deserialize the pemurrage period for the given account is participating in
2021-02-05 12:56:57 +01:00
function accountPeriod ( address _account ) public view returns ( uint256 ) {
2021-02-02 21:14:41 +01:00
return ( uint256 ( account [ _account ] ) & 0xffffffffffffffffffffffff0000000000000000000000000000000000000000 ) >> 160 ;
}
2021-02-03 09:16:01 +01:00
// Save the given demurrage period as the currently participation period for the given address
2021-02-02 21:14:41 +01:00
function registerAccountPeriod ( address _account , uint256 _period ) private returns ( bool ) {
account [ _account ] &= 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff ;
account [ _account ] |= bytes32 ( _period << 160 ) ;
incrementRedistributionParticipants ( ) ;
}
2021-02-05 12:02:05 +01:00
// Determine whether the unit number is rounded down, rounded up or evenly divides.
// Returns 0 if evenly distributed, or the remainder as a positive number
2021-02-05 12:56:57 +01:00
// A _numParts value 0 will be interpreted as the value 1
2021-02-05 12:02:05 +01:00
function remainder ( uint256 _numParts , uint256 _sumWhole ) public pure returns ( uint256 ) {
uint256 unit ;
uint256 truncatedResult ;
2021-02-05 12:56:57 +01:00
if ( _numParts == 0 ) { // no division by zero please
2021-02-05 18:52:01 +01:00
revert ( ' ERR_NUMPARTS_ZERO ' ) ;
2021-02-05 12:02:05 +01:00
}
2021-02-05 12:56:57 +01:00
require ( _numParts < _sumWhole ) ; // At least you are never LESS than the sum of your parts. Think about that.
2021-02-05 12:02:05 +01:00
unit = _sumWhole / _numParts ;
truncatedResult = unit * _numParts ;
return _sumWhole - truncatedResult ;
}
2021-02-05 12:32:17 +01:00
2021-02-05 19:02:01 +01:00
// Called in the edge case where participant number is 0. It will override the participant count to 1.
// Returns the remainder sent to the sink address
2021-02-05 18:52:01 +01:00
function applyDefaultRedistribution ( bytes32 _redistribution ) private returns ( uint256 ) {
uint256 redistributionSupply ;
uint256 redistributionPeriod ;
uint256 unit ;
uint256 truncatedResult ;
redistributionSupply = toRedistributionSupply ( _redistribution ) ;
unit = ( redistributionSupply * taxLevel ) / 1000000 ;
truncatedResult = ( unit * 1000000 ) / taxLevel ;
if ( truncatedResult < redistributionSupply ) {
redistributionPeriod = toRedistributionPeriod ( _redistribution ) ; // since we reuse period here, can possibly be optimized by passing period instead
2021-02-05 19:02:01 +01:00
redistributions [ redistributionPeriod - 1 ] &= 0x0000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff ; // just to be safe, zero out all participant count data, in this case there will be only one
redistributions [ redistributionPeriod - 1 ] |= 0x8000000001000000000000000000000000000000000000000000000000000000 ;
2021-02-05 18:52:01 +01:00
}
increaseBaseBalance ( sinkAddress , unit ) ; //truncatedResult);
return unit ;
}
2021-02-05 12:32:17 +01:00
// sets the remainder bit for the given period and books the remainder to the sink address balance
// returns false if no change was made
function applyRemainderOnPeriod ( uint256 _remainder , uint256 _period ) private returns ( bool ) {
2021-02-05 18:52:01 +01:00
uint256 periodSupply ;
2021-02-05 12:32:17 +01:00
if ( _remainder == 0 ) {
return false ;
}
2021-02-05 18:52:01 +01:00
redistributions [ _period - 1 ] |= 0x8000000000000000000000000000000000000000000000000000000000000000 ;
periodSupply = toRedistributionSupply ( redistributions [ _period - 1 ] ) ;
increaseBaseBalance ( sinkAddress , periodSupply - _remainder ) ;
2021-02-05 12:02:05 +01:00
return true ;
}
2021-02-03 09:16:01 +01:00
// Recalculate the demurrage modifier for the new period
2021-02-05 12:02:05 +01:00
// After this, all REPORTED balances will have been reduced by the corresponding ratio (but the effecive totalsupply stays the same)
2021-02-02 11:26:44 +01:00
function applyTax ( ) public returns ( uint256 ) {
2021-02-05 12:02:05 +01:00
bytes32 currentRedistribution ;
2021-02-02 11:26:44 +01:00
bytes32 nextRedistribution ;
2021-02-02 16:10:21 +01:00
uint256 currentPeriod ;
2021-02-05 12:02:05 +01:00
uint256 currentParticipants ;
uint256 currentRemainder ;
2021-02-02 11:26:44 +01:00
2021-02-05 12:02:05 +01:00
currentRedistribution = checkPeriod ( ) ;
if ( currentRedistribution == bytes32 ( 0x00 ) ) {
2021-02-02 19:09:13 +01:00
return demurrageModifier ;
2021-02-02 11:26:44 +01:00
}
2021-02-02 16:10:21 +01:00
demurrageModifier -= ( demurrageModifier * taxLevel ) / 1000000 ;
2021-02-05 12:02:05 +01:00
currentPeriod = toRedistributionPeriod ( currentRedistribution ) ;
2021-02-02 19:09:13 +01:00
nextRedistribution = toRedistribution ( 0 , totalSupply , currentPeriod + 1 ) ;
2021-02-02 11:26:44 +01:00
redistributions . push ( nextRedistribution ) ;
2021-02-05 12:02:05 +01:00
currentParticipants = toRedistributionParticipants ( currentRedistribution ) ;
2021-02-05 18:52:01 +01:00
if ( currentParticipants == 0 ) {
currentRemainder = applyDefaultRedistribution ( currentRedistribution ) ;
} else {
currentRemainder = remainder ( currentParticipants , totalSupply ) ; // we can use totalSupply directly because it will always be the same as the recorded supply on the current redistribution
applyRemainderOnPeriod ( currentRemainder , currentPeriod ) ;
}
2021-02-05 12:02:05 +01:00
emit Taxed ( currentPeriod , currentRemainder ) ;
2021-02-02 11:26:44 +01:00
return demurrageModifier ;
}
2021-02-03 09:16:01 +01:00
// Calculate a value reduced by demurrage by the given period
2021-02-03 09:00:24 +01:00
function toTaxPeriodAmount ( uint256 _value , uint256 _period ) public view returns ( uint256 ) {
uint256 valueFactor ;
// TODO: doesn't work for solidity as floats are missing and using ints linearly increases the order of magnitude
// valueFactor = 1000000 * (((1000000-taxLevel)/1000000) ** _period);
valueFactor = 1000000 ;
for ( uint256 i = 0 ; i < _period ; i ++ ) {
2021-02-06 05:32:01 +01:00
valueFactor = valueFactor - ( ( valueFactor * taxLevel ) / 1000000 ) ;
2021-02-03 09:00:24 +01:00
}
return ( valueFactor * _value ) / 1000000 ;
2021-02-02 16:46:27 +01:00
}
2021-02-03 09:16:01 +01:00
// If the given account is participating in a period and that period has been crossed
// THEN increase the base value of the account with its share of the value reduction of the period
2021-02-02 21:14:41 +01:00
function applyRedistributionOnAccount ( address _account ) public returns ( bool ) {
bytes32 periodRedistribution ;
uint256 supply ;
uint256 participants ;
2021-02-03 09:00:24 +01:00
uint256 baseValue ;
2021-02-02 21:14:41 +01:00
uint256 value ;
uint256 period ;
period = accountPeriod ( _account ) ;
2021-02-03 09:00:24 +01:00
if ( period == 0 || period >= actualPeriod ( ) ) {
2021-02-02 21:14:41 +01:00
return false ;
}
2021-02-03 09:00:24 +01:00
periodRedistribution = redistributions [ period - 1 ] ;
2021-02-02 21:14:41 +01:00
participants = toRedistributionParticipants ( periodRedistribution ) ;
if ( participants == 0 ) {
2021-02-05 12:56:57 +01:00
return false ;
2021-02-02 21:14:41 +01:00
}
2021-02-05 12:56:57 +01:00
2021-02-02 21:14:41 +01:00
supply = toRedistributionSupply ( periodRedistribution ) ;
2021-02-06 05:32:01 +01:00
baseValue = ( ( supply / participants ) * ( taxLevel ) / 1000000 ) ;
value = toTaxPeriodAmount ( baseValue , period - 1 ) ;
2021-02-02 21:14:41 +01:00
account [ _account ] &= bytes32 ( 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff ) ;
2021-02-05 12:56:57 +01:00
increaseBaseBalance ( _account , value ) ;
2021-02-02 21:14:41 +01:00
emit Redistribution ( _account , period , value ) ;
return true ;
2021-02-02 16:46:27 +01:00
}
2021-02-05 09:22:36 +01:00
// Inflates the given amount according to the current demurrage modifier
function toBaseAmount ( uint256 _value ) public view returns ( uint256 ) {
return ( _value * 1000000 ) / demurrageModifier ;
}
2021-02-03 09:16:01 +01:00
// ERC20, triggers tax and/or redistribution
2021-02-02 16:46:27 +01:00
function transfer ( address _to , uint256 _value ) public returns ( bool ) {
uint256 baseValue ;
bool result ;
2021-02-02 19:09:13 +01:00
applyTax ( ) ;
2021-02-03 09:00:24 +01:00
applyRedistributionOnAccount ( msg . sender ) ;
2021-02-02 21:14:41 +01:00
// TODO: Prefer to truncate the result, instead it seems to round to nearest :/
2021-02-05 09:22:36 +01:00
baseValue = toBaseAmount ( _value ) ;
2021-02-02 16:46:27 +01:00
result = transferBase ( msg . sender , _to , baseValue ) ;
2021-02-02 21:14:41 +01:00
2021-02-02 16:10:21 +01:00
return result ;
}
2021-02-03 09:16:01 +01:00
// ERC20 transfer backend for transfer, transferFrom
2021-02-02 16:46:27 +01:00
function transferBase ( address _from , address _to , uint256 _value ) private returns ( bool ) {
uint256 period ;
2021-02-05 13:00:14 +01:00
decreaseBaseBalance ( _from , _value ) ;
increaseBaseBalance ( _to , _value ) ;
2021-02-02 16:46:27 +01:00
period = actualPeriod ( ) ;
2021-02-03 09:00:24 +01:00
if ( _value > 0 && accountPeriod ( _from ) != period ) {
2021-02-02 17:53:35 +01:00
registerAccountPeriod ( _from , period ) ;
2021-02-02 16:46:27 +01:00
}
2021-02-02 16:10:21 +01:00
return true ;
2021-02-02 11:26:44 +01:00
}
2021-02-05 09:22:36 +01:00
// ERC20, triggers tax and/or redistribution
function transferFrom ( address _from , address _to , uint256 _value ) public returns ( bool ) {
uint256 baseValue ;
bool result ;
applyTax ( ) ;
applyRedistributionOnAccount ( msg . sender ) ;
baseValue = toBaseAmount ( _value ) ;
require ( allowance [ _from ] [ msg . sender ] >= baseValue ) ;
result = transferBase ( _from , _to , baseValue ) ;
return result ;
}
// ERC20, triggers tax and/or redistribution
function approve ( address _spender , uint256 _value ) public returns ( bool ) {
uint256 baseValue ;
applyTax ( ) ;
applyRedistributionOnAccount ( msg . sender ) ;
baseValue = toBaseAmount ( _value ) ;
allowance [ msg . sender ] [ _spender ] += baseValue ;
emit Approval ( msg . sender , _spender , _value ) ;
return true ;
}
2021-02-02 11:26:44 +01:00
}