This repository has been archived on 2023-03-01. You can view files and clone it, but cannot push or open issues or pull requests.
erc20-demurrage-token-archive/solidity/RedistributedDemurrageToken.sol

361 lines
14 KiB
Solidity
Raw Normal View History

2021-02-02 11:26:44 +01:00
pragma solidity > 0.6.11;
// SPDX-License-Identifier: GPL-3.0-or-later
// TODO: assign bitmask values to contants
2021-02-02 11:26:44 +01:00
contract RedistributedDemurrageToken {
address public owner;
uint256 public decimals;
string public name;
string public symbol;
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
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;
sinkAddress = _defaultSinkAddress;
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
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;
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
function decreaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
2021-02-02 21:14:41 +01:00
uint256 oldBalance;
uint256 newBalance;
oldBalance = getBaseBalance(_account);
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();
totalSupply += _amount;
increaseBaseBalance(_beneficiary, _amount);
2021-02-02 16:10:21 +01:00
emit Mint(msg.sender, _beneficiary, _amount);
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-05 12:32:17 +01:00
redistribution |= bytes32((_participants & 0x7fffffffff) << 216);
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
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;
currentRedistribution |= participants << 216;
2021-02-03 09:00:24 +01:00
//emit Debug(participants);
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
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;
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
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
// 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;
if (_numParts == 0) { // no division by zero please
return _sumWhole;
2021-02-05 12:02:05 +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
// 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) {
bytes32 redistribution;
redistribution = redistributions[_period-1];
if (_remainder == 0) {
return false;
}
redistribution |= 0x8000000000000000000000000000000000000000000000000000000000000000;
increaseBaseBalance(sinkAddress, _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);
currentRemainder = remainder(currentParticipants, totalSupply); // we can use totalSupply directly because it will always be the same as the recorded supply on the current redistribution
2021-02-05 12:32:17 +01:00
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++) {
valueFactor = (valueFactor * taxLevel) / 1000000;
}
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) {
return false;
2021-02-02 21:14:41 +01:00
}
2021-02-02 21:14:41 +01:00
supply = toRedistributionSupply(periodRedistribution);
2021-02-03 09:00:24 +01:00
// TODO: Make sure value for balance increases round down, and that we can do a single allocation to a sink account with the difference. We can use the highest bit in "participants" for that.
baseValue = supply / participants;
value = toTaxPeriodAmount(baseValue, period-1);
2021-02-02 21:14:41 +01:00
account[_account] &= bytes32(0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff);
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;
if (!decreaseBaseBalance(_from, _value)) {
2021-02-02 16:46:27 +01:00
revert('ERR_TX_DECREASEBALANCE');
}
if (!increaseBaseBalance(_to, _value)) {
2021-02-02 16:46:27 +01:00
revert('ERR_TX_INCREASEBALANCE');
}
period = actualPeriod();
2021-02-03 09:00:24 +01:00
if (_value > 0 && accountPeriod(_from) != period) {
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
}