6 Commits

6 changed files with 185 additions and 392 deletions

View File

@@ -1,72 +0,0 @@
# standard imports
import os
import unittest
import json
import logging
# third-party imports
import web3
import eth_tester
import eth_abi
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
logging.getLogger('web3').setLevel(logging.WARNING)
logging.getLogger('eth.vm').setLevel(logging.WARNING)
testdir = os.path.dirname(__file__)
TAX_LEVEL = 10000 * 2 # 2%
class Test(unittest.TestCase):
contract = None
def setUp(self):
eth_params = eth_tester.backends.pyevm.main.get_default_genesis_params({
'gas_limit': 9000000,
})
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.bin'), 'r')
self.bytecode = f.read()
f.close()
f = open(os.path.join(testdir, '../../solidity/RedistributedDemurrageToken.json'), 'r')
self.abi = json.load(f)
f.close()
backend = eth_tester.PyEVMBackend(eth_params)
self.eth_tester = eth_tester.EthereumTester(backend)
provider = web3.Web3.EthereumTesterProvider(self.eth_tester)
self.w3 = web3.Web3(provider)
self.sink_address = self.w3.eth.accounts[9]
def tearDown(self):
pass
def test_gas_changeperiod(self):
period = 43200
for i in range(5):
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), period, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(start_block)
start_time = b['timestamp']
period_seconds = period * 60
self.eth_tester.time_travel(start_time + period_seconds + (60 * (10 ** i)))
tx_hash = contract.functions.changePeriod().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
print('{} ({}): {}'.format(i, 60 * (10 ** i), r['gasUsed']))
if __name__ == '__main__':
unittest.main()

View File

@@ -3,7 +3,6 @@ import os
import unittest
import json
import logging
import datetime
# third-party imports
import web3
@@ -19,11 +18,9 @@ logging.getLogger('eth.vm').setLevel(logging.WARNING)
testdir = os.path.dirname(__file__)
#BLOCKTIME = 5 # seconds
TAX_LEVEL = int(10000 * 2) # 2%
# calc "1-(0.98)^(1/518400)" <- 518400 = 30 days of blocks
# 0.00000003897127107225
TAX_LEVEL = 10000 * 2 # 2%
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
PERIOD = 1
PERIOD = 10
class Test(unittest.TestCase):
@@ -51,15 +48,12 @@ class Test(unittest.TestCase):
self.sink_address = self.w3.eth.accounts[9]
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
self.start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(self.start_block)
self.start_time = b['timestamp']
def tearDown(self):
pass
@@ -67,31 +61,10 @@ class Test(unittest.TestCase):
def test_hello(self):
self.assertEqual(self.contract.functions.actualPeriod().call(), 1)
self.eth_tester.time_travel(self.start_time + 61)
self.eth_tester.mine_blocks(PERIOD)
self.assertEqual(self.contract.functions.actualPeriod().call(), 2)
def test_apply_demurrage(self):
modifier = 10 * (10 ** 37)
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_modifier &= (1 << 128) - 1
self.assertEqual(modifier, demurrage_modifier)
self.eth_tester.time_travel(self.start_time + 59)
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_modifier &= (1 << 128) - 1
self.assertEqual(modifier, demurrage_modifier)
self.eth_tester.time_travel(self.start_time + 61)
tx_hash = self.contract.functions.applyDemurrage().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_modifier &= (1 << 128) - 1
self.assertEqual(int(98 * (10 ** 36)), demurrage_modifier)
def test_mint(self):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
@@ -107,26 +80,6 @@ class Test(unittest.TestCase):
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
self.assertEqual(balance, 2000)
self.eth_tester.time_travel(self.start_time + 61)
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
self.assertEqual(balance, int(2000 * 0.98))
def test_base_amount(self):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
self.eth_tester.time_travel(self.start_time + 61)
self.contract.functions.applyDemurrage().transact()
demurrage_modifier = self.contract.functions.demurrageModifier().call()
demurrage_amount = self.contract.functions.toDemurrageAmount(demurrage_modifier).call()
logg.debug('d {} {}'.format(demurrage_modifier.to_bytes(32, 'big').hex(), demurrage_amount))
a = self.contract.functions.toBaseAmount(1000).call();
self.assertEqual(a, 1020)
def test_transfer(self):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
@@ -175,5 +128,62 @@ class Test(unittest.TestCase):
self.assertEqual(balance_alice, 500)
def test_apply_tax(self):
self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact()
self.eth_tester.mine_blocks(PERIOD)
tx_hash = self.contract.functions.applyTax().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(self.contract.functions.redistributionCount().call(), 2)
self.assertEqual(self.contract.functions.demurrageModifier().call(), 980000)
self.eth_tester.mine_blocks(PERIOD)
tx_hash = self.contract.functions.applyTax().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(self.contract.functions.redistributionCount().call(), 3)
self.assertEqual(self.contract.functions.demurrageModifier().call(), 960400)
def test_tax_balance(self):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
self.eth_tester.mine_blocks(PERIOD)
tx_hash = self.contract.functions.applyTax().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
balance = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
self.assertEqual(balance, 980)
def test_taxed_transfer(self):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
self.eth_tester.mine_blocks(PERIOD)
tx_hash = self.contract.functions.applyTax().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
self.assertEqual(balance_alice, 980000)
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500000).transact({'from': self.w3.eth.accounts[1]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
logg.debug('r {}'.format(r))
self.assertEqual(r.status, 1)
balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call()
balance_alice_trunc = int(balance_alice/1000)*1000
self.assertEqual(balance_alice_trunc, 480000)
balance_bob = self.contract.functions.balanceOf(self.w3.eth.accounts[2]).call()
balance_bob_trunc = int(balance_bob/1000)*1000
self.assertEqual(balance_bob_trunc, 500000)
if __name__ == '__main__':
unittest.main()

View File

@@ -20,7 +20,7 @@ testdir = os.path.dirname(__file__)
#BLOCKTIME = 5 # seconds
TAX_LEVEL = 10000 * 2 # 2%
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
PERIOD = 1
PERIOD = 2
class Test(unittest.TestCase):
@@ -47,15 +47,12 @@ class Test(unittest.TestCase):
self.sink_address = self.w3.eth.accounts[9]
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
self.start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(self.start_block)
self.start_time = b['timestamp']
def tearDown(self):
pass
@@ -66,14 +63,15 @@ class Test(unittest.TestCase):
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
self.eth_tester.time_travel(self.start_time + 61)
tx_hash = self.contract.functions.changePeriod().transact()
self.eth_tester.mine_blocks(PERIOD * 10)
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
logg.debug('r {}'.format(r));
self.assertEqual(r.status, 1)
redistribution = self.contract.functions.redistributions(1).call()
self.assertEqual(2, self.contract.functions.toRedistributionPeriod(redistribution).call())
self.assertEqual(2, self.contract.functions.actualPeriod().call())
period = self.contract.functions.accountPeriod(self.w3.eth.accounts[1]).call()
self.assertEqual(period, 12)
if __name__ == '__main__':

View File

@@ -19,7 +19,8 @@ logging.getLogger('eth.vm').setLevel(logging.WARNING)
testdir = os.path.dirname(__file__)
#BLOCKTIME = 5 # seconds
TAX_LEVEL = int((10000 * 2) * (10 ** 32)) # 2%
TAX_LEVEL = 10000 * 2 # 2%
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
PERIOD = 10
@@ -61,20 +62,14 @@ class Test(unittest.TestCase):
def test_tax_period(self):
t = self.contract.functions.taxLevel().call()
logg.debug('taxlevel {}'.format(t))
a = self.contract.functions.toTaxPeriodAmount(1000000, 0).call()
self.assertEqual(a, 1000000)
self.assertEqual(1000000, a)
a = self.contract.functions.toTaxPeriodAmount(1000000, 1).call()
self.assertEqual(a, 980000)
self.assertEqual(980000, a)
a = self.contract.functions.toTaxPeriodAmount(1000000, 2).call()
self.assertEqual(a, 960400)
a = self.contract.functions.toTaxPeriodAmount(980000, 1).call()
self.assertEqual(a, 960400)
self.assertEqual(960400, a)
def test_fractional_state(self):

View File

@@ -20,7 +20,7 @@ testdir = os.path.dirname(__file__)
#BLOCKTIME = 5 # seconds
TAX_LEVEL = 10000 * 2 # 2%
#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month
PERIOD = 1
PERIOD = 20
class Test(unittest.TestCase):
@@ -47,15 +47,13 @@ class Test(unittest.TestCase):
self.sink_address = self.w3.eth.accounts[9]
c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode)
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL * (10 ** 32), PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
tx_hash = c.constructor('Foo Token', 'FOO', 6, TAX_LEVEL, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress)
self.start_block = self.w3.eth.blockNumber
b = self.w3.eth.getBlock(self.start_block)
self.start_time = b['timestamp']
logg.debug('starting at block number {}'.format(self.start_block))
def tearDown(self):
pass
@@ -70,28 +68,26 @@ class Test(unittest.TestCase):
# TODO: check receipt log outputs
@unittest.skip('foo')
def test_redistribution_storage(self):
self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact()
self.contract.functions.mintTo(self.w3.eth.accounts[2], 1000000).transact()
self.contract.functions.mintTo(self.w3.eth.accounts[1], 2000).transact()
external_address = web3.Web3.toChecksumAddress('0x' + os.urandom(20).hex())
tx_hash = self.contract.functions.transfer(external_address, 1000000).transact({'from': self.w3.eth.accounts[2]})
tx_hash = self.contract.functions.transfer(external_address, 999999).transact({'from': self.w3.eth.accounts[1]})
tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]})
r = self.w3.eth.getTransactionReceipt(tx_hash)
logg.debug('tx before {}'.format(r))
self.assertEqual(r.status, 1)
self.eth_tester.time_travel(self.start_time + 61)
self.eth_tester.mine_blocks(PERIOD)
redistribution = self.contract.functions.redistributions(0).call();
self.assertEqual(redistribution.hex(), '000000000000000000000000f42400000000010000000000001e848000000001')
self.assertEqual(redistribution.hex(), '000000000100000000000000000000000000000000000007d000000000000001')
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[0], 1000000).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(r.status, 1)
redistribution = self.contract.functions.redistributions(1).call()
self.assertEqual(redistribution.hex(), '000000000000000000000000ef4200000000000000000000002dc6c000000002')
self.assertEqual(redistribution.hex(), '000000000000000000000000000000000000000000000f4a1000000000000002')
def test_redistribution_balance_on_zero_participants(self):
@@ -99,27 +95,24 @@ class Test(unittest.TestCase):
tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], supply).transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
self.eth_tester.time_travel(self.start_time + 61)
self.eth_tester.mine_blocks(PERIOD)
tx_hash = self.contract.functions.applyDemurrage().transact()
tx_hash = self.contract.functions.applyTax().transact()
r = self.w3.eth.getTransactionReceipt(tx_hash)
logg.debug('r {}'.format(r))
self.assertEqual(r.status, 1)
tx_hash = self.contract.functions.changePeriod().transact()
rr = self.w3.eth.getTransactionReceipt(tx_hash)
self.assertEqual(rr.status, 1)
redistribution = self.contract.functions.redistributions(0).call();
supply = self.contract.functions.totalSupply().call()
sink_increment = int(supply * (TAX_LEVEL / 1000000))
for l in r['logs']:
if l.topics[0].hex() == '0xa0717e54e02bd9829db5e6e998aec0ae9de796b8d150a3cc46a92ab869697755': # event Decayed(uint256,uint256,uint256,uint256)
if l.topics[0].hex() == '0x337db9c77a0769d770641c73e3282be23b15e2bddd830c219461dec832313389': # event Taxed(uint256,uint256)
period = int.from_bytes(l.topics[1], 'big')
self.assertEqual(period, 2)
self.assertEqual(period, 1)
b = bytes.fromhex(l.data[2:])
remainder = int.from_bytes(b, 'big')
self.assertEqual(remainder, int((1000000 - TAX_LEVEL) * (10 ** 32)))
self.assertEqual(remainder, sink_increment)
logg.debug('period {} remainder {}'.format(period, remainder))
sink_balance = self.contract.functions.balanceOf(self.sink_address).call()
@@ -147,15 +140,14 @@ class Test(unittest.TestCase):
r = self.w3.eth.getTransactionReceipt(tx_hash)
# No cheating!
self.contract.functions.transfer(self.w3.eth.accounts[3], spend_amount).transact({'from': self.w3.eth.accounts[3]})
# No cheapskating!
# Too low
self.contract.functions.transfer(external_address, spend_amount-1).transact({'from': self.w3.eth.accounts[4]})
self.assertEqual(r.status, 1)
self.eth_tester.time_travel(self.start_time + 61)
self.eth_tester.mine_blocks(PERIOD)
self.contract.functions.applyDemurrage().transact()
self.contract.functions.changePeriod().transact()
self.contract.functions.applyTax().transact()
bummer_balance = self.contract.functions.balanceOf(self.w3.eth.accounts[3]).call()
self.assertEqual(bummer_balance, mint_amount - (mint_amount * (TAX_LEVEL / 1000000)))

View File

@@ -11,17 +11,14 @@ contract RedistributedDemurrageToken {
uint256 public decimals;
uint256 public totalSupply;
uint256 public minimumParticipantSpend;
uint256 constant ppmDivider = 100000000000000000000000000000000;
uint256 public immutable periodStart; // timestamp
uint256 public immutable periodDuration; // duration in SECONDS
uint256 public immutable taxLevel; // PPM per MINUTE
uint256 public demurrageModifier; // PPM uint128(block) | uint128(ppm)
uint256 public periodStart;
uint256 public periodDuration;
uint256 public taxLevel; // PPM
uint256 public demurrageModifier; // PPM
//bytes32[] public redistributions; // uint1(isFractional) | uint1(unused) | uint38(participants) | uint160(value) | uint56(period)
bytes32[] public redistributions; // uint1(isFractional) | uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
//mapping (address => bytes32) account; // uint20(unused) | uint56(period) | uint160(value)
mapping (address => bytes32) account; // uint152(unused) | uint32(period) | uint72(value)
bytes32[] public redistributions; // uint1(isFractional) | uint1(unused) | uint38(participants) | uint160(value) | uint56(period)
mapping (address => bytes32) account; // uint20(unused) | uint56(period) | uint160(value)
mapping (address => bool) minter;
mapping (address => mapping (address => uint256 ) ) allowance; // holder -> spender -> amount (amount is subject to demurrage)
@@ -30,23 +27,22 @@ contract RedistributedDemurrageToken {
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
event Debug(bytes32 _foo);
event Decayed(uint256 indexed _period, uint256 indexed _periodCount, uint256 indexed _oldAmount, uint256 _newAmount);
//event Debug(uint256 _foo);
event Taxed(uint256 indexed _period, uint256 remainder);
event Redistribution(address indexed _account, uint256 indexed _period, uint256 _value);
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress) public {
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint32 _taxLevel, uint256 _period, address _defaultSinkAddress) public {
owner = msg.sender;
minter[owner] = true;
periodStart = block.timestamp;
periodDuration = _periodMinutes * 60;
periodStart = block.number;
periodDuration = _period;
taxLevel = _taxLevel;
name = _name;
symbol = _symbol;
decimals = _decimals;
demurrageModifier = ppmDivider * 1000000; // Emulates 38 decimal places
demurrageModifier |= (1 << 128);
taxLevel = _taxLevelMinute; // 38 decimal places
demurrageModifier = 1000000;
sinkAddress = _defaultSinkAddress;
bytes32 initialRedistribution = toRedistribution(0, 1000000, 0, 1);
bytes32 initialRedistribution = toRedistribution(0, 0, 1);
redistributions.push(initialRedistribution);
minimumParticipantSpend = 10 ** uint256(_decimals);
}
@@ -57,53 +53,30 @@ contract RedistributedDemurrageToken {
minter[_minter] = true;
return true;
}
/// ERC20
function balanceOf(address _account) public view returns (uint256) {
uint256 baseBalance;
uint256 anchorDemurrageAmount;
uint256 anchorDemurragePeriod;
uint256 currentDemurrageAmount;
uint256 periodCount;
baseBalance = getBaseBalance(_account);
anchorDemurrageAmount = toDemurrageAmount(demurrageModifier);
anchorDemurragePeriod = toDemurragePeriod(demurrageModifier);
periodCount = actualPeriod() - toDemurragePeriod(demurrageModifier);
currentDemurrageAmount = decayBy(anchorDemurrageAmount, periodCount);
return (baseBalance * currentDemurrageAmount) / (ppmDivider * 1000000);
uint256 baseBalance = getBaseBalance(_account);
uint256 inverseModifier = 1000000 - demurrageModifier;
uint256 balanceModifier = (inverseModifier * baseBalance) / 1000000;
return baseBalance - balanceModifier;
}
/// Balance unmodified by demurrage
function getBaseBalance(address _account) private view returns (uint256) {
//return uint256(account[_account]) & 0x00ffffffffffffffffffffffffffffffffffffffff;
return uint256(account[_account]) & 0xffffffffffffffffff;
return uint256(account[_account]) & 0x00ffffffffffffffffffffffffffffffffffffffff;
}
/// Increases base balance for a single account
function increaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
uint256 oldBalance;
uint256 newBalance;
uint256 workAccount;
workAccount = uint256(account[_account]); // | (newBalance & 0xffffffffffffffffff);
if (_delta == 0) {
return false;
}
oldBalance = getBaseBalance(_account);
newBalance = oldBalance + _delta;
require(uint160(newBalance) > uint160(oldBalance), 'ERR_WOULDWRAP'); // revert if increase would result in a wrapped value
//account[_account] &= bytes32(0xfffffffffffffffffffffff0000000000000000000000000000000000000000);
//account[_account] = bytes32(uint256(account[_account]) & 0xfffffffffffffffffffffffffffffffffffffffffffff000000000000000000);
workAccount &= 0xfffffffffffffffffffffffffffffffffffffffffffff000000000000000000;
//account[_account] |= bytes32(newBalance & 0x00ffffffffffffffffffffffffffffffffffffffff);
workAccount |= newBalance & 0xffffffffffffffffff;
account[_account] = bytes32(workAccount);
account[_account] &= bytes32(0xffffffffffffffffffffffff0000000000000000000000000000000000000000);
account[_account] |= bytes32(newBalance & 0x00ffffffffffffffffffffffffffffffffffffffff);
return true;
}
@@ -111,71 +84,52 @@ contract RedistributedDemurrageToken {
function decreaseBaseBalance(address _account, uint256 _delta) private returns (bool) {
uint256 oldBalance;
uint256 newBalance;
uint256 workAccount;
workAccount = uint256(account[_account]); // | (newBalance & 0xffffffffffffffffff);
if (_delta == 0) {
return false;
}
oldBalance = getBaseBalance(_account);
require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard
newBalance = oldBalance - _delta;
//account[_account] &= bytes32(0xffffffffffffffffffffffff0000000000000000000000000000000000000000);
workAccount &= 0xfffffffffffffffffffffffffffffffffffffffffffff000000000000000000;
//account[_account] |= bytes32(newBalance & 0x00ffffffffffffffffffffffffffffffffffffffff);
workAccount |= newBalance & 0xffffffffffffffffff;
account[_account] = bytes32(workAccount);
account[_account] &= bytes32(0xffffffffffffffffffffffff0000000000000000000000000000000000000000);
account[_account] |= bytes32(newBalance & 0x00ffffffffffffffffffffffffffffffffffffffff);
return true;
}
// 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) {
uint256 baseAmount;
require(minter[msg.sender]);
changePeriod();
baseAmount = _amount;
// TODO: get base amount for minting
applyTax();
totalSupply += _amount;
increaseBaseBalance(_beneficiary, baseAmount);
increaseBaseBalance(_beneficiary, _amount);
emit Mint(msg.sender, _beneficiary, _amount);
saveRedistributionSupply();
return true;
}
// Deserializes the redistribution word
// uint1(isFractional) | uint95(unused) | uint20(demurrageModifier) | uint36(participants) | uint72(value) | uint32(period)
function toRedistribution(uint256 _participants, uint256 _demurrageModifierPpm, uint256 _value, uint256 _period) private pure returns(bytes32) {
function toRedistribution(uint256 _participants, uint256 _value, uint256 _period) private pure returns(bytes32) {
bytes32 redistribution;
redistribution |= bytes32((_demurrageModifierPpm & 0x0fffff) << 140);
redistribution |= bytes32((_participants & 0x0fffffffff) << 104);
redistribution |= bytes32((_value & 0xffffffffffffffffff) << 32);
redistribution |= bytes32(_period & 0xffffffff);
redistribution |= bytes32((_participants & 0x7fffffffff) << 216);
redistribution |= bytes32((_value & 0xffffffffffffffffffffffff) << 56);
redistribution |= bytes32(_period & 0xffffffffffffff);
return redistribution;
}
// Serializes the demurrage period part of the redistribution word
function toRedistributionPeriod(bytes32 redistribution) public pure returns (uint256) {
return uint256(redistribution) & 0xffffffff;
return uint256(redistribution & 0x00000000000000000000000000000000000000000000000000ffffffffffffff);
}
// Serializes the supply part of the redistribution word
function toRedistributionSupply(bytes32 redistribution) public pure returns (uint256) {
return uint256(redistribution & 0x00000000000000000000000000000000000000ffffffffffffffffff00000000) >> 32;
return uint256(redistribution & 0x0000000000ffffffffffffffffffffffffffffffffffffffff00000000000000) >> 56;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionParticipants(bytes32 redistribution) public pure returns (uint256) {
return uint256(redistribution & 0x00000000000000000000000000000fffffffff00000000000000000000000000) >> 104;
}
// Serializes the number of participants part of the redistribution word
function toRedistributionDemurrageModifier(bytes32 redistribution) public pure returns (uint256) {
return uint256(redistribution & 0x000000000000000000000000fffff00000000000000000000000000000000000) >> 140;
return uint256(redistribution & 0x7fffffffff000000000000000000000000000000000000000000000000000000) >> 216;
}
// Client accessor to the redistributions array length
@@ -185,19 +139,16 @@ contract RedistributedDemurrageToken {
// Add number of participants for the current redistribution period by one
function incrementRedistributionParticipants() private returns (bool) {
bytes32 currentRedistribution;
uint256 tmpRedistribution;
uint256 currentRedistribution;
uint256 participants;
currentRedistribution = redistributions[redistributions.length-1];
participants = toRedistributionParticipants(currentRedistribution) + 1;
tmpRedistribution = uint256(currentRedistribution);
tmpRedistribution &= 0xfffffffffffffffffffffffffffff000000000ffffffffffffffffffffffffff;
tmpRedistribution |= (participants & 0x0fffffffff) << 104;
currentRedistribution = uint256(redistributions[redistributions.length-1]);
participants = ((currentRedistribution & 0x7fffffffff000000000000000000000000000000000000000000000000000000) >> 216) + 1;
currentRedistribution &= 0x8000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffff;
currentRedistribution |= participants << 216;
redistributions[redistributions.length-1] = bytes32(tmpRedistribution);
return true;
//emit Debug(participants);
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
}
// Save the current total supply amount to the current redistribution period
@@ -205,16 +156,15 @@ contract RedistributedDemurrageToken {
uint256 currentRedistribution;
currentRedistribution = uint256(redistributions[redistributions.length-1]);
currentRedistribution &= 0xffffffffffffffffffffffffffffffffffffff000000000000000000ffffffff;
currentRedistribution |= totalSupply << 32;
currentRedistribution &= 0xffffffffff0000000000000000000000000000000000000000ffffffffffffff;
currentRedistribution |= totalSupply << 56;
redistributions[redistributions.length-1] = bytes32(currentRedistribution);
return true;
}
// Get the demurrage period of the current block number
function actualPeriod() public view returns (uint256) {
return (block.timestamp - periodStart) / periodDuration + 1;
return (block.number - periodStart) / periodDuration + 1;
}
// Add an entered demurrage period to the redistribution array
@@ -232,17 +182,14 @@ contract RedistributedDemurrageToken {
// Deserialize the pemurrage period for the given account is participating in
function accountPeriod(address _account) public view returns (uint256) {
//return (uint256(account[_account]) & 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) >> 160;
return (uint256(account[_account]) & 0x00000000000000000000000000000000000000ffffffff000000000000000000) >> 72;
return (uint256(account[_account]) & 0xffffffffffffffffffffffff0000000000000000000000000000000000000000) >> 160;
}
// Save the given demurrage period as the currently participation period for the given address
function registerAccountPeriod(address _account, uint256 _period) private returns (bool) {
//account[_account] &= 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
account[_account] &= 0xffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffffff;
account[_account] |= bytes32(_period << 72);
account[_account] &= 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
account[_account] |= bytes32(_period << 160);
incrementRedistributionParticipants();
return true;
}
// Determine whether the unit number is rounded down, rounded up or evenly divides.
@@ -277,11 +224,11 @@ contract RedistributedDemurrageToken {
if (truncatedResult < redistributionSupply) {
redistributionPeriod = toRedistributionPeriod(_redistribution); // since we reuse period here, can possibly be optimized by passing period instead
redistributions[redistributionPeriod-1] &= 0xfffffffffffffffffffffffffffff000000000ffffffffffffffffffffffffff; // just to be safe, zero out all participant count data, in this case there will be only one
redistributions[redistributionPeriod-1] |= 0x8000000000000000000000000000000000000100000000000000000000000000;
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;
}
increaseBaseBalance(sinkAddress, unit / ppmDivider); //truncatedResult);
increaseBaseBalance(sinkAddress, unit); //truncatedResult);
return unit;
}
@@ -294,7 +241,6 @@ contract RedistributedDemurrageToken {
return false;
}
// is this needed?
redistributions[_period-1] |= 0x8000000000000000000000000000000000000000000000000000000000000000;
periodSupply = toRedistributionSupply(redistributions[_period-1]);
@@ -302,80 +248,22 @@ contract RedistributedDemurrageToken {
return true;
}
function toDemurrageAmount(uint256 _demurrage) public pure returns (uint256) {
return _demurrage & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
}
function toDemurragePeriod(uint256 _demurrage) public pure returns (uint256) {
return (_demurrage & 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000) >> 128;
}
function applyDemurrage() public returns (bool) {
uint256 epochPeriodCount;
uint256 periodCount;
uint256 lastDemurrageAmount;
uint256 newDemurrageAmount;
epochPeriodCount = actualPeriod();
//epochPeriodCount = (block.timestamp - periodStart) / periodDuration; // toDemurrageTime(demurrageModifier);
periodCount = epochPeriodCount - toDemurragePeriod(demurrageModifier);
if (periodCount == 0) {
return false;
}
lastDemurrageAmount = toDemurrageAmount(demurrageModifier);
newDemurrageAmount = decayBy(lastDemurrageAmount, periodCount);
demurrageModifier = 0;
demurrageModifier |= (newDemurrageAmount & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff);
demurrageModifier |= (epochPeriodCount << 128);
emit Decayed(epochPeriodCount, periodCount, lastDemurrageAmount, newDemurrageAmount);
return true;
}
// Return timestamp of start of period threshold
function getPeriodTimeDelta(uint256 _periodCount) public view returns (uint256) {
return periodStart + (_periodCount * periodDuration);
}
// Amount of demurrage cycles inbetween the current timestamp and the given target time
function demurrageCycles(uint256 _target) public view returns (uint256) {
return (block.timestamp - _target) / 60;
}
// Recalculate the demurrage modifier for the new period
// After this, all REPORTED balances will have been reduced by the corresponding ratio (but the effecive totalsupply stays the same)
//function applyTax() public returns (uint256) {
function changePeriod() public returns (bool) {
function applyTax() public returns (uint256) {
bytes32 currentRedistribution;
bytes32 nextRedistribution;
uint256 currentPeriod;
uint256 currentParticipants;
uint256 currentRemainder;
uint256 currentDemurrageAmount;
uint256 nextRedistributionDemurrage;
uint256 demurrageCounts;
uint256 periodTimestamp;
currentRedistribution = checkPeriod();
if (currentRedistribution == bytes32(0x00)) {
return false;
return demurrageModifier;
}
demurrageModifier -= (demurrageModifier * taxLevel) / 1000000;
currentPeriod = toRedistributionPeriod(currentRedistribution);
periodTimestamp = getPeriodTimeDelta(currentPeriod);
applyDemurrage();
currentDemurrageAmount = toDemurrageAmount(demurrageModifier);
demurrageCounts = demurrageCycles(periodTimestamp);
if (demurrageCounts > 0) {
nextRedistributionDemurrage = growBy(currentDemurrageAmount, demurrageCounts) / ppmDivider;
} else {
nextRedistributionDemurrage = currentDemurrageAmount / ppmDivider;
}
nextRedistribution = toRedistribution(0, nextRedistributionDemurrage, totalSupply, currentPeriod + 1);
emit Debug(bytes32(currentDemurrageAmount));
nextRedistribution = toRedistribution(0, totalSupply, currentPeriod + 1);
redistributions.push(nextRedistribution);
currentParticipants = toRedistributionParticipants(currentRedistribution);
@@ -385,35 +273,19 @@ contract RedistributedDemurrageToken {
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);
}
return true;
}
function growBy(uint256 _value, uint256 _period) public view returns (uint256) {
uint256 valueFactor;
uint256 truncatedTaxLevel;
// TODO: if can't get to work, reverse the iteration from current period.
valueFactor = 1000000;
truncatedTaxLevel = taxLevel / ppmDivider;
for (uint256 i = 0; i < _period; i++) {
valueFactor = valueFactor + ((valueFactor * truncatedTaxLevel) / 1000000);
}
return (valueFactor * _value) / 1000000;
emit Taxed(currentPeriod, currentRemainder);
return demurrageModifier;
}
// Calculate a value reduced by demurrage by the given period
// TODO: higher precision
function decayBy(uint256 _value, uint256 _period) public view returns (uint256) {
function toTaxPeriodAmount(uint256 _value, uint256 _period) public view returns (uint256) {
uint256 valueFactor;
uint256 truncatedTaxLevel;
// TODO: if can't get to work, reverse the iteration from current period.
// 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;
truncatedTaxLevel = taxLevel / ppmDivider;
for (uint256 i = 0; i < _period; i++) {
valueFactor = valueFactor - ((valueFactor * truncatedTaxLevel) / 1000000);
valueFactor = valueFactor - ((valueFactor * taxLevel) / 1000000);
}
return (valueFactor * _value) / 1000000;
}
@@ -439,11 +311,10 @@ contract RedistributedDemurrageToken {
}
supply = toRedistributionSupply(periodRedistribution);
baseValue = ((supply / participants) * (taxLevel / 1000000)) / ppmDivider;
value = decayBy(baseValue, period - 1);
baseValue = ((supply / participants) * (taxLevel) / 1000000);
value = toTaxPeriodAmount(baseValue, period - 1);
//account[_account] &= bytes32(0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff);
account[_account] &= bytes32(0xffffffffffffffffffffffffffffffffffffff00000000ffffffffffffffffff);
account[_account] &= bytes32(0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff);
increaseBaseBalance(_account, value);
emit Redistribution(_account, period, value);
@@ -452,20 +323,7 @@ contract RedistributedDemurrageToken {
// Inflates the given amount according to the current demurrage modifier
function toBaseAmount(uint256 _value) public view returns (uint256) {
return (_value * ppmDivider * 1000000) / toDemurrageAmount(demurrageModifier);
}
// ERC20, triggers tax and/or redistribution
function approve(address _spender, uint256 _value) public returns (bool) {
uint256 baseValue;
changePeriod();
applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
allowance[msg.sender][_spender] += baseValue;
emit Approval(msg.sender, _spender, _value);
return true;
return (_value * 1000000) / demurrageModifier;
}
// ERC20, triggers tax and/or redistribution
@@ -473,7 +331,7 @@ contract RedistributedDemurrageToken {
uint256 baseValue;
bool result;
changePeriod();
applyTax();
applyRedistributionOnAccount(msg.sender);
// TODO: Prefer to truncate the result, instead it seems to round to nearest :/
@@ -483,22 +341,6 @@ contract RedistributedDemurrageToken {
return result;
}
// ERC20, triggers tax and/or redistribution
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
uint256 baseValue;
bool result;
changePeriod();
applyRedistributionOnAccount(msg.sender);
baseValue = toBaseAmount(_value);
require(allowance[_from][msg.sender] >= baseValue);
result = transferBase(_from, _to, baseValue);
return result;
}
// ERC20 transfer backend for transfer, transferFrom
function transferBase(address _from, address _to, uint256 _value) private returns (bool) {
uint256 period;
@@ -512,4 +354,32 @@ contract RedistributedDemurrageToken {
}
return true;
}
// 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;
}
}