erc20-demurrage-token

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

commit 8e60539245e230e4c8e7bd0c1901fe2744f0f256
Author: nolash <dev@holbrook.no>
Date:   Tue,  2 Feb 2021 11:26:44 +0100

Initial commit

Diffstat:
Apython/tests/test_basic.py | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asolidity/Makefile | 14++++++++++++++
Asolidity/RedistributedDemurrageToken.sol | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 177 insertions(+), 0 deletions(-)

diff --git a/python/tests/test_basic.py b/python/tests/test_basic.py @@ -0,0 +1,82 @@ +# 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__) + +#BLOCKTIME = 5 # seconds +TAX_LEVEL = 10000 * 2 # 2% +#PERIOD = int(60/BLOCKTIME) * 60 * 24 * 30 # month +PERIOD = 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) + c = self.w3.eth.contract(abi=self.abi, bytecode=self.bytecode) + tx_hash = c.constructor('Foo Token', 'FOO', TAX_LEVEL, PERIOD).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 + + def tearDown(self): + pass + + + 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) + + 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(), TAX_LEVEL); + + 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); + +if __name__ == '__main__': + unittest.main() + + diff --git a/solidity/Makefile b/solidity/Makefile @@ -0,0 +1,14 @@ +SOLC = /usr/bin/solc + +all: + $(SOLC) RedistributedDemurrageToken.sol --abi --evm-version byzantium | awk 'NR>3' > RedistributedDemurrageToken.json + $(SOLC) RedistributedDemurrageToken.sol --bin --evm-version byzantium | awk 'NR>3' > RedistributedDemurrageToken.bin + truncate -s -1 RedistributedDemurrageToken.bin + +test: all + python ../python/tests/test_basic.py + +install: all + cp -v RedistributedDemurrageToken.{json,bin} ../python/eth_address_declarator/data/ + +.PHONY: test install diff --git a/solidity/RedistributedDemurrageToken.sol b/solidity/RedistributedDemurrageToken.sol @@ -0,0 +1,81 @@ +pragma solidity > 0.6.11; + +// SPDX-License-Identifier: GPL-3.0-or-later + +contract RedistributedDemurrageToken { + + address public owner; + uint256 public decimals; + string public name; + string public symbol; + uint256 public totalSupply; + + uint256 public periodStart; + uint256 public periodDuration; + uint32 public taxLevel; + uint256 public demurrageModifier; + + bytes32[] redistributions; // uint40(participants) uint160(value) uint56(period) + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + constructor(string memory _name, string memory _symbol, uint32 _taxLevel, uint256 _period) { + owner = msg.sender; + periodStart = block.number; + periodDuration = _period; + taxLevel = _taxLevel; + name = _name; + symbol = _symbol; + decimals = 6; + bytes32 initialRedistribution = toRedistribution(0, 1, 0); + redistributions.push(initialRedistribution); + } + + function toRedistribution(uint256 _participants, uint256 _value, uint256 _period) private pure returns(bytes32) { + bytes32 redistribution; + redistribution |= bytes32((_participants & 0xffffffffff) << 215); + redistribution |= bytes32((_value & 0xffffffffffffffffffffffff) << 55); + redistribution |= bytes32((_period & 0xffffffffffffff) << 55); + return redistribution; + } + + function toRedistributionPeriod(bytes32 redistribution) public pure returns (uint256) { + return uint256(redistribution & bytes7(0xffffffffffffff)); + } + + function redistributionCount() public view returns (uint256) { + return redistributions.length; + } + + function actualPeriod() public view returns (uint256) { + return (block.number - periodStart) / periodDuration; + } + + function checkPeriod() private view returns (bytes32) { + bytes32 lastRedistribution = redistributions[redistributions.length-1]; + uint256 currentPeriod = this.actualPeriod(); + if (currentPeriod < toRedistributionPeriod(lastRedistribution)) { + return bytes32(0x00); + } + return lastRedistribution; + } + + function applyTax() public returns (uint256) { + bytes32 pendingRedistribution; + bytes32 nextRedistribution; + + pendingRedistribution = checkPeriod(); + if (pendingRedistribution == bytes32(0x00)) { + return demurrageModifier; + } + demurrageModifier += taxLevel; + nextRedistribution = toRedistribution(0, actualPeriod(), 0); + redistributions.push(nextRedistribution); + return demurrageModifier; + } + + function noop() public returns (uint256) { + return 0; + } +}