erc20-demurrage-token

ERC20 token with redistributed continual demurrage
Log | Files | Refs | README

commit 1a2b3bab370a01197aa043961c501b267df9e3c3
parent 8e60539245e230e4c8e7bd0c1901fe2744f0f256
Author: nolash <dev@holbrook.no>
Date:   Tue,  2 Feb 2021 16:10:21 +0100

Add transfer, mint

Diffstat:
Mpython/tests/test_basic.py | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msolidity/RedistributedDemurrageToken.sol | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 151 insertions(+), 16 deletions(-)

diff --git a/python/tests/test_basic.py b/python/tests/test_basic.py @@ -22,6 +22,7 @@ TAX_LEVEL = 10000 * 2 # 2% #PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month PERIOD = 2 + class Test(unittest.TestCase): contract = None @@ -55,26 +56,103 @@ class Test(unittest.TestCase): pass + @unittest.skip('foo') def test_period(self): self.assertEqual(self.contract.functions.actualPeriod().call(), 0) self.eth_tester.mine_blocks(PERIOD) self.assertEqual(self.contract.functions.actualPeriod().call(), 1) + + @unittest.skip('foo') + def test_mint(self): + tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).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, 1024); + + tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 976).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, 2000); + + + def test_transfer(self): + tx_hash = self.contract.functions.mintTo(self.w3.eth.accounts[1], 1024).transact(); + r = self.w3.eth.getTransactionReceipt(tx_hash); + self.assertEqual(r.status, 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); + self.assertEqual(r.status, 1); + + balance_alice = self.contract.functions.balanceOf(self.w3.eth.accounts[1]).call(); + self.assertEqual(balance_alice, 524); + + balance_bob = self.contract.functions.balanceOf(self.w3.eth.accounts[2]).call(); + self.assertEqual(balance_bob, 500); + + + @unittest.skip('foo') def test_apply_tax(self): - tx = self.contract.functions.noop().buildTransaction() - logg.debug('gas {}'.format(self.w3.eth.estimateGas(tx))) + 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(), 2); - self.assertEqual(self.contract.functions.demurrageModifier().call(), TAX_LEVEL); + 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) + + + @unittest.skip('foo') + 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(self.contract.functions.redistributionCount().call(), 3); - self.assertEqual(self.contract.functions.demurrageModifier().call(), TAX_LEVEL * 2); + 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) + 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/solidity/RedistributedDemurrageToken.sol b/solidity/RedistributedDemurrageToken.sol @@ -12,26 +12,70 @@ contract RedistributedDemurrageToken { uint256 public periodStart; uint256 public periodDuration; - uint32 public taxLevel; - uint256 public demurrageModifier; + uint256 public taxLevel; // PPM + uint256 public demurrageModifier; // PPM bytes32[] redistributions; // uint40(participants) uint160(value) uint56(period) + mapping (address => bytes32) account; + mapping (address => bool) minter; 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 _amount); constructor(string memory _name, string memory _symbol, uint32 _taxLevel, uint256 _period) { owner = msg.sender; + minter[owner] = true; periodStart = block.number; periodDuration = _period; taxLevel = _taxLevel; name = _name; symbol = _symbol; decimals = 6; + demurrageModifier = 1000000; bytes32 initialRedistribution = toRedistribution(0, 1, 0); redistributions.push(initialRedistribution); } + function addMinter(address _minter) public returns (bool) { + require(msg.sender == owner); + minter[_minter] = true; + return true; + } + + function balanceOf(address _account) public view returns (uint256) { + uint256 baseBalance = getBaseBalance(_account); + uint256 inverseModifier = 1000000 - demurrageModifier; + uint256 balanceModifier = (inverseModifier * baseBalance) / 1000000; + return baseBalance - balanceModifier; + } + + function getBaseBalance(address _account) private view returns (uint256) { + return uint256(account[_account]) & 0x00ffffffffffffffffffffffffffffffffffffffff; + } + + function increaseBalance(address _account, uint256 _delta) private returns (bool) { + uint256 oldBalance = getBaseBalance(_account); + account[_account] &= bytes20(0x00); + account[_account] |= bytes32((oldBalance + _delta) & 0x00ffffffffffffffffffffffffffffffffffffffff); + return true; + } + + function decreaseBalance(address _account, uint256 _delta) private returns (bool) { + uint256 oldBalance = getBaseBalance(_account); + account[_account] &= bytes20(0x00); + account[_account] |= bytes32((oldBalance - _delta) & 0x00ffffffffffffffffffffffffffffffffffffffff); + return true; + } + + function mintTo(address _beneficiary, uint256 _amount) external returns (bool) { + require(minter[msg.sender]); + + increaseBalance(_beneficiary, _amount); + emit Mint(msg.sender, _beneficiary, _amount); + return true; + } + function toRedistribution(uint256 _participants, uint256 _value, uint256 _period) private pure returns(bytes32) { bytes32 redistribution; redistribution |= bytes32((_participants & 0xffffffffff) << 215); @@ -64,18 +108,31 @@ contract RedistributedDemurrageToken { function applyTax() public returns (uint256) { bytes32 pendingRedistribution; bytes32 nextRedistribution; + uint256 currentPeriod; pendingRedistribution = checkPeriod(); if (pendingRedistribution == bytes32(0x00)) { return demurrageModifier; } - demurrageModifier += taxLevel; - nextRedistribution = toRedistribution(0, actualPeriod(), 0); + demurrageModifier -= (demurrageModifier * taxLevel) / 1000000; + // this should increment for one single period at at time + currentPeriod = toRedistributionPeriod(pendingRedistribution); + nextRedistribution = toRedistribution(0, currentPeriod + 1, 0); redistributions.push(nextRedistribution); return demurrageModifier; } - function noop() public returns (uint256) { - return 0; + function transfer(address _to, uint256 _value) public returns (bool ) { + //&uint256 baseValue = (_value * 1000000) / demurrageModifier; + uint256 baseValue = (_value * 1000000) / demurrageModifier; + bool result = transferBase(_to, baseValue); + emit Transfer(msg.sender, _to, _value); + return result; + } + + function transferBase(address _to, uint256 _value) private returns (bool) { + decreaseBalance(msg.sender, _value); + increaseBalance(_to, _value); + return true; } }