diff --git a/python/tests/test_basic.py b/python/tests/test_basic.py index 90a711c..e1a07a9 100644 --- a/python/tests/test_basic.py +++ b/python/tests/test_basic.py @@ -3,6 +3,7 @@ import os import unittest import json import logging +import datetime # third-party imports import web3 @@ -18,9 +19,11 @@ logging.getLogger('eth.vm').setLevel(logging.WARNING) testdir = os.path.dirname(__file__) #BLOCKTIME = 5 # seconds -TAX_LEVEL = 10000 * 2 # 2% +TAX_LEVEL = int(10000 * 2) # 2% +# calc "1-(0.98)^(1/518400)" <- 518400 = 30 days of blocks +# 0.00000003897127107225 #PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month -PERIOD = 10 +PERIOD = 1 class Test(unittest.TestCase): @@ -48,12 +51,15 @@ 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, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]}) + 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) 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 @@ -61,10 +67,31 @@ class Test(unittest.TestCase): def test_hello(self): self.assertEqual(self.contract.functions.actualPeriod().call(), 1) - self.eth_tester.mine_blocks(PERIOD) + self.eth_tester.time_travel(self.start_time + 61) 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) @@ -80,6 +107,26 @@ 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() @@ -128,62 +175,5 @@ 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() diff --git a/python/tests/test_period.py b/python/tests/test_period.py index 7b83ff3..5f7a60a 100644 --- a/python/tests/test_period.py +++ b/python/tests/test_period.py @@ -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 = 2 +PERIOD = 1 class Test(unittest.TestCase): @@ -47,12 +47,15 @@ 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, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]}) + 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) 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 @@ -63,15 +66,14 @@ class Test(unittest.TestCase): r = self.w3.eth.getTransactionReceipt(tx_hash) self.assertEqual(r.status, 1) - 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]}) + self.eth_tester.time_travel(self.start_time + 61) + tx_hash = self.contract.functions.changePeriod().transact() r = self.w3.eth.getTransactionReceipt(tx_hash) - logg.debug('r {}'.format(r)); self.assertEqual(r.status, 1) - period = self.contract.functions.accountPeriod(self.w3.eth.accounts[1]).call() - self.assertEqual(period, 12) + redistribution = self.contract.functions.redistributions(1).call() + self.assertEqual(2, self.contract.functions.toRedistributionPeriod(redistribution).call()) + self.assertEqual(2, self.contract.functions.actualPeriod().call()) if __name__ == '__main__': diff --git a/python/tests/test_pure.py b/python/tests/test_pure.py index 6ad7201..54665e2 100644 --- a/python/tests/test_pure.py +++ b/python/tests/test_pure.py @@ -19,8 +19,7 @@ logging.getLogger('eth.vm').setLevel(logging.WARNING) testdir = os.path.dirname(__file__) #BLOCKTIME = 5 # seconds -TAX_LEVEL = 10000 * 2 # 2% -#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month +TAX_LEVEL = int((10000 * 2) * (10 ** 32)) # 2% PERIOD = 10 @@ -62,14 +61,20 @@ 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(1000000, a) + self.assertEqual(a, 1000000) a = self.contract.functions.toTaxPeriodAmount(1000000, 1).call() - self.assertEqual(980000, a) + self.assertEqual(a, 980000) a = self.contract.functions.toTaxPeriodAmount(1000000, 2).call() - self.assertEqual(960400, a) + self.assertEqual(a, 960400) + + a = self.contract.functions.toTaxPeriodAmount(980000, 1).call() + self.assertEqual(a, 960400) def test_fractional_state(self): diff --git a/python/tests/test_redistribution.py b/python/tests/test_redistribution.py index e8dcaa3..9a95911 100644 --- a/python/tests/test_redistribution.py +++ b/python/tests/test_redistribution.py @@ -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 = 20 +PERIOD = 1 class Test(unittest.TestCase): @@ -47,13 +47,15 @@ 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, PERIOD, self.sink_address).transact({'from': self.w3.eth.accounts[0]}) + 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) self.contract = self.w3.eth.contract(abi=self.abi, address=r.contractAddress) self.start_block = self.w3.eth.blockNumber - logg.debug('starting at block number {}'.format(self.start_block)) + b = self.w3.eth.getBlock(self.start_block) + self.start_time = b['timestamp'] + def tearDown(self): pass @@ -68,26 +70,28 @@ 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], 2000).transact() + self.contract.functions.mintTo(self.w3.eth.accounts[1], 1000000).transact() + self.contract.functions.mintTo(self.w3.eth.accounts[2], 1000000).transact() - tx_hash = self.contract.functions.transfer(self.w3.eth.accounts[2], 500).transact({'from': self.w3.eth.accounts[1]}) + 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]}) r = self.w3.eth.getTransactionReceipt(tx_hash) logg.debug('tx before {}'.format(r)) self.assertEqual(r.status, 1) - self.eth_tester.mine_blocks(PERIOD) + self.eth_tester.time_travel(self.start_time + 61) redistribution = self.contract.functions.redistributions(0).call(); - self.assertEqual(redistribution.hex(), '000000000100000000000000000000000000000000000007d000000000000001') + self.assertEqual(redistribution.hex(), '000000000100000000000000000000000000000000001e848000000000000001') 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(), '000000000000000000000000000000000000000000000f4a1000000000000002') + self.assertEqual(redistribution.hex(), '000000000000000000000000000000000000000000002dc6c000000000000002') def test_redistribution_balance_on_zero_participants(self): @@ -95,24 +99,27 @@ 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.mine_blocks(PERIOD) + self.eth_tester.time_travel(self.start_time + 61) - tx_hash = self.contract.functions.applyTax().transact() + tx_hash = self.contract.functions.applyDemurrage().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() == '0x337db9c77a0769d770641c73e3282be23b15e2bddd830c219461dec832313389': # event Taxed(uint256,uint256) + if l.topics[0].hex() == '0xa0717e54e02bd9829db5e6e998aec0ae9de796b8d150a3cc46a92ab869697755': # event Decayed(uint256,uint256,uint256,uint256) period = int.from_bytes(l.topics[1], 'big') - self.assertEqual(period, 1) + self.assertEqual(period, 2) b = bytes.fromhex(l.data[2:]) remainder = int.from_bytes(b, 'big') - self.assertEqual(remainder, sink_increment) + self.assertEqual(remainder, int((1000000 - TAX_LEVEL) * (10 ** 32))) logg.debug('period {} remainder {}'.format(period, remainder)) sink_balance = self.contract.functions.balanceOf(self.sink_address).call() @@ -140,14 +147,15 @@ 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]}) - # Too low + # Cheapskate! self.contract.functions.transfer(external_address, spend_amount-1).transact({'from': self.w3.eth.accounts[4]}) self.assertEqual(r.status, 1) - self.eth_tester.mine_blocks(PERIOD) + self.eth_tester.time_travel(self.start_time + 61) - self.contract.functions.applyTax().transact() + self.contract.functions.applyDemurrage().transact() + self.contract.functions.changePeriod().transact() bummer_balance = self.contract.functions.balanceOf(self.w3.eth.accounts[3]).call() self.assertEqual(bummer_balance, mint_amount - (mint_amount * (TAX_LEVEL / 1000000))) diff --git a/solidity/RedistributedDemurrageToken.sol b/solidity/RedistributedDemurrageToken.sol index 74a3e50..1958c51 100644 --- a/solidity/RedistributedDemurrageToken.sol +++ b/solidity/RedistributedDemurrageToken.sol @@ -11,11 +11,12 @@ contract RedistributedDemurrageToken { uint256 public decimals; uint256 public totalSupply; uint256 public minimumParticipantSpend; + uint256 constant ppmDivider = 100000000000000000000000000000000; - uint256 public periodStart; - uint256 public periodDuration; - uint256 public taxLevel; // PPM - uint256 public demurrageModifier; // PPM + 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) bytes32[] public redistributions; // uint1(isFractional) | uint1(unused) | uint38(participants) | uint160(value) | uint56(period) mapping (address => bytes32) account; // uint20(unused) | uint56(period) | uint160(value) @@ -28,19 +29,20 @@ contract RedistributedDemurrageToken { event Approval(address indexed _owner, address indexed _spender, uint256 _value); event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value); //event Debug(uint256 _foo); - event Taxed(uint256 indexed _period, uint256 remainder); + event Decayed(uint256 indexed _period, uint256 indexed _periodCount, uint256 indexed _oldAmount, uint256 _newAmount); event Redistribution(address indexed _account, uint256 indexed _period, uint256 _value); - constructor(string memory _name, string memory _symbol, uint8 _decimals, uint32 _taxLevel, uint256 _period, address _defaultSinkAddress) public { + constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _taxLevelMinute, uint256 _periodMinutes, address _defaultSinkAddress) public { owner = msg.sender; minter[owner] = true; - periodStart = block.number; - periodDuration = _period; - taxLevel = _taxLevel; + periodStart = block.timestamp; + periodDuration = _periodMinutes * 60; name = _name; symbol = _symbol; decimals = _decimals; - demurrageModifier = 1000000; + demurrageModifier = ppmDivider * 1000000; // Emulates 38 decimal places + demurrageModifier |= (1 << 128); + taxLevel = _taxLevelMinute; // 38 decimal places sinkAddress = _defaultSinkAddress; bytes32 initialRedistribution = toRedistribution(0, 0, 1); redistributions.push(initialRedistribution); @@ -53,13 +55,24 @@ contract RedistributedDemurrageToken { minter[_minter] = true; return true; } - + /// ERC20 function balanceOf(address _account) public view returns (uint256) { - uint256 baseBalance = getBaseBalance(_account); - uint256 inverseModifier = 1000000 - demurrageModifier; - uint256 balanceModifier = (inverseModifier * baseBalance) / 1000000; - return baseBalance - balanceModifier; + uint256 baseBalance; + uint256 anchorDemurrageAmount; + uint256 anchorDemurragePeriod; + uint256 currentDemurrageAmount; + uint256 periodCount; + + baseBalance = getBaseBalance(_account); + anchorDemurrageAmount = toDemurrageAmount(demurrageModifier); + anchorDemurragePeriod = toDemurragePeriod(demurrageModifier); + + periodCount = actualPeriod() - toDemurragePeriod(demurrageModifier); + + currentDemurrageAmount = toTaxPeriodAmount(anchorDemurrageAmount, periodCount); + + return (baseBalance * currentDemurrageAmount) / (ppmDivider * 1000000); } /// Balance unmodified by demurrage @@ -71,7 +84,11 @@ contract RedistributedDemurrageToken { function increaseBaseBalance(address _account, uint256 _delta) private returns (bool) { uint256 oldBalance; uint256 newBalance; - + + 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 @@ -85,6 +102,10 @@ contract RedistributedDemurrageToken { uint256 oldBalance; uint256 newBalance; + if (_delta == 0) { + return false; + } + oldBalance = getBaseBalance(_account); require(oldBalance >= _delta, 'ERR_OVERSPEND'); // overspend guard newBalance = oldBalance - _delta; @@ -98,8 +119,8 @@ contract RedistributedDemurrageToken { function mintTo(address _beneficiary, uint256 _amount) external returns (bool) { require(minter[msg.sender]); - // TODO: get base amount for minting - applyTax(); + applyDemurrage(); + changePeriod(); totalSupply += _amount; increaseBaseBalance(_beneficiary, _amount); emit Mint(msg.sender, _beneficiary, _amount); @@ -164,7 +185,7 @@ contract RedistributedDemurrageToken { // Get the demurrage period of the current block number function actualPeriod() public view returns (uint256) { - return (block.number - periodStart) / periodDuration + 1; + return (block.timestamp - periodStart) / periodDuration + 1; } // Add an entered demurrage period to the redistribution array @@ -228,7 +249,7 @@ contract RedistributedDemurrageToken { redistributions[redistributionPeriod-1] |= 0x8000000001000000000000000000000000000000000000000000000000000000; } - increaseBaseBalance(sinkAddress, unit); //truncatedResult); + increaseBaseBalance(sinkAddress, unit / ppmDivider); //truncatedResult); return unit; } @@ -248,9 +269,40 @@ 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 = toTaxPeriodAmount(lastDemurrageAmount, periodCount); + demurrageModifier = 0; + demurrageModifier |= (newDemurrageAmount & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff); + demurrageModifier |= (epochPeriodCount << 128); + emit Decayed(epochPeriodCount, periodCount, lastDemurrageAmount, newDemurrageAmount); + return true; + } + // 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 applyTax() public returns (uint256) { + function changePeriod() public returns (uint256) { bytes32 currentRedistribution; bytes32 nextRedistribution; uint256 currentPeriod; @@ -261,7 +313,7 @@ contract RedistributedDemurrageToken { if (currentRedistribution == bytes32(0x00)) { return demurrageModifier; } - demurrageModifier -= (demurrageModifier * taxLevel) / 1000000; + //demurrageModifier -= (demurrageModifier * taxLevel) / 1000000; currentPeriod = toRedistributionPeriod(currentRedistribution); nextRedistribution = toRedistribution(0, totalSupply, currentPeriod + 1); redistributions.push(nextRedistribution); @@ -273,19 +325,20 @@ 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); } - emit Taxed(currentPeriod, currentRemainder); return demurrageModifier; } // Calculate a value reduced by demurrage by the given period function toTaxPeriodAmount(uint256 _value, uint256 _period) public view returns (uint256) { uint256 valueFactor; + uint256 truncatedTaxLevel; - // 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); + // 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 * taxLevel) / 1000000); + valueFactor = valueFactor - ((valueFactor * truncatedTaxLevel) / 1000000); } return (valueFactor * _value) / 1000000; } @@ -311,7 +364,7 @@ contract RedistributedDemurrageToken { } supply = toRedistributionSupply(periodRedistribution); - baseValue = ((supply / participants) * (taxLevel) / 1000000); + baseValue = ((supply / participants) * (taxLevel / 1000000)) / ppmDivider; value = toTaxPeriodAmount(baseValue, period - 1); account[_account] &= bytes32(0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff); @@ -323,7 +376,21 @@ contract RedistributedDemurrageToken { // Inflates the given amount according to the current demurrage modifier function toBaseAmount(uint256 _value) public view returns (uint256) { - return (_value * 1000000) / demurrageModifier; + return (_value * ppmDivider * 1000000) / toDemurrageAmount(demurrageModifier); + } + + // ERC20, triggers tax and/or redistribution + function approve(address _spender, uint256 _value) public returns (bool) { + uint256 baseValue; + + applyDemurrage(); + changePeriod(); + applyRedistributionOnAccount(msg.sender); + + baseValue = toBaseAmount(_value); + allowance[msg.sender][_spender] += baseValue; + emit Approval(msg.sender, _spender, _value); + return true; } // ERC20, triggers tax and/or redistribution @@ -331,7 +398,8 @@ contract RedistributedDemurrageToken { uint256 baseValue; bool result; - applyTax(); + applyDemurrage(); + changePeriod(); applyRedistributionOnAccount(msg.sender); // TODO: Prefer to truncate the result, instead it seems to round to nearest :/ @@ -341,6 +409,23 @@ 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; + + applyDemurrage(); + 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; @@ -354,32 +439,4 @@ 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; - } }