├── .gitignore ├── .gitattributes ├── package.json ├── CREDITS ├── truffle.js ├── truffle-config.js ├── migrations └── 1_initial_migration.js ├── utils ├── assertRevert.js ├── sequentialPromiseNamed.js └── expectedException.js ├── yarn.lock ├── contracts ├── Migrations.sol ├── mock │ └── TaxableTokenMock.sol ├── CappedToken.sol ├── Taxable.sol ├── TaxableToken.sol └── HasWhiteList.sol ├── LICENSE ├── test ├── CappedToken.js ├── Taxable.js ├── HasWhiteList.js └── TaxableToken.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "zeppelin-solidity": "^1.12.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | N: Giulio Rebuffo 2 | E: giulio.rebuffo@gmail.com 3 | ========================== 4 | #Add your name here -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 5 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 5 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /utils/assertRevert.js: -------------------------------------------------------------------------------- 1 | async function assertRevert(promise) { 2 | try { 3 | await promise; 4 | assert.fail('Expected revert not received'); 5 | } catch (error) { 6 | const revertFound = error.message.search('revert') >= 0; 7 | assert(revertFound, `Expected "revert", got ${error} instead`); 8 | } 9 | } 10 | 11 | module.exports = assertRevert; 12 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | zeppelin-solidity@^1.12.0: 6 | version "1.12.0" 7 | resolved "https://registry.yarnpkg.com/zeppelin-solidity/-/zeppelin-solidity-1.12.0.tgz#427edf8e0041ec8600875d3cb8fc9395f33ccfff" 8 | integrity sha512-dgjPPnTmx14hAbTeOpTKemDeDCDdwglS0nquOAJG8h5o9zlb43FZafQSrMlIUUSp1EisDZfehrp5loGEYXHZBA== 9 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/mock/TaxableTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "../TaxableToken.sol"; 4 | 5 | contract TaxableTokenMock is TaxableToken{ 6 | function TaxableTokenMock(uint256 supply, uint256 _minimunFee, uint8 _percentage,string _name,string _symbol,uint8 _decimals) public{ 7 | //we don't need test for this 8 | require(_percentage < 100); 9 | require(supply > 0); 10 | require(_decimals < 18); 11 | name = _name; 12 | symbol = _symbol; 13 | decimals = _decimals; 14 | minimunFee = _minimunFee; 15 | percentage = _percentage; 16 | totalSupply_ = supply; 17 | cap = supply; 18 | balances[msg.sender] = supply; 19 | } 20 | } -------------------------------------------------------------------------------- /contracts/CappedToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.11; 2 | 3 | import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol"; 4 | 5 | 6 | /** 7 | * @title Capped token 8 | * @dev Mintable token with a token cap. 9 | */ 10 | contract CappedToken is MintableToken { 11 | 12 | uint256 public cap; 13 | 14 | /** 15 | * @dev Function to mint tokens 16 | * @param _to The address that will receive the minted tokens. 17 | * @param _amount The amount of tokens to mint. 18 | * @return A boolean that indicates if the operation was successful. 19 | */ 20 | function mint(address _to, uint256 _amount) onlyOwner canMint public returns (bool) { 21 | require(totalSupply_.add(_amount) <= cap); 22 | return super.mint(_to, _amount); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /utils/sequentialPromiseNamed.js: -------------------------------------------------------------------------------- 1 | const Promise = require("bluebird"); 2 | 3 | /** 4 | * @param {!Object.>>} promiseObject. Each key maps to a function 5 | * that returns a promise. 6 | * @returns {!Promise.>} The results of the promises passed to the function. 7 | */ 8 | module.exports = function sequentialPromiseNamed(promiseObject) { 9 | const result = Object.keys(promiseObject).reduce( 10 | (reduced, key) => { 11 | return { 12 | chain: reduced.chain 13 | .then(() => promiseObject[ key ]()) 14 | .then(result => reduced.results[ key ] = result), 15 | results: reduced.results 16 | }; 17 | }, 18 | { 19 | chain: Promise.resolve(), 20 | results: {} 21 | }); 22 | return result.chain.then(() => result.results); 23 | }; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Giulio rebuffo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/CappedToken.js: -------------------------------------------------------------------------------- 1 | var assertRevert = require("../utils/assertRevert.js"); 2 | 3 | var CappedToken = artifacts.require('TaxableTokenMock'); 4 | 5 | contract('Capped', function (accounts) { 6 | const cap = 1000; 7 | 8 | let token; 9 | 10 | beforeEach(async function () { 11 | token = await CappedToken.new(cap,10,10,"token","tkn",10); 12 | }); 13 | 14 | it('should start with the correct cap', async function () { 15 | let _cap = await token.cap(); 16 | 17 | assert.equal(cap,_cap); 18 | }); 19 | 20 | it('should mint when amount is less than cap', async function () { 21 | await token.burn(100,{from : accounts[0]}) 22 | const result = await token.mint(accounts[0], 100); 23 | assert.equal(result.logs[0].event, 'Mint'); 24 | }); 25 | 26 | it('should fail to mint if the ammount exceeds the cap', async function () { 27 | await token.burn(cap,{from : accounts[0]}) 28 | await token.mint(accounts[0], cap - 1); 29 | await assertRevert(token.mint(accounts[0], 100)); 30 | }); 31 | 32 | it('should fail to mint after cap is reached', async function () { 33 | await token.burn(cap,{from : accounts[0]}) 34 | await token.mint(accounts[0], cap); 35 | await assertRevert(token.mint(accounts[0], 1)); 36 | }); 37 | }); -------------------------------------------------------------------------------- /utils/expectedException.js: -------------------------------------------------------------------------------- 1 | module.exports = function expectedExceptionPromise(action, gasToUse) { 2 | return new Promise(function (resolve, reject) { 3 | try { 4 | resolve(action()); 5 | } catch(e) { 6 | reject(e); 7 | } 8 | }) 9 | .then(function (txObj) { 10 | return typeof txn === "string" 11 | ? web3.eth.getTransactionReceiptMined(txObj) // regular tx hash 12 | : typeof txObj.receipt !== "undefined" 13 | ? txObj.receipt // truffle-contract function call 14 | : typeof txObj.transactionHash === "string" 15 | ? web3.eth.getTransactionReceiptMined(txObj.transactionHash) // deployment 16 | : txObj; // Unknown last case 17 | }) 18 | .then(function (receipt) { 19 | // We are in Geth or the tx wrongly passed 20 | assert.equal(receipt.gasUsed, gasToUse, "should have used all the gas"); 21 | }) 22 | .catch(function (e) { 23 | if ((e + "").indexOf("invalid JUMP") > -1 || 24 | (e + "").indexOf("out of gas") > -1 || 25 | (e + "").indexOf("invalid opcode") > -1) { 26 | // We are in TestRPC 27 | } else if ((e + "").indexOf("please check your gas amount") > -1) { 28 | // We are in Geth for a deployment 29 | } else { 30 | throw e; 31 | } 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /contracts/Taxable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | /* 6 | * @title Has White List 7 | * @dev make tokens taxable(you have to handle those variables as you wish in your token) 8 | */ 9 | contract Taxable is Ownable{ 10 | //events 11 | event LogPercentageChanges(address sender,uint8 oldPercentage,uint8 newPercentage); 12 | event LogMinimunFeeChanges(address sender,uint256 oldMinimunFee,uint256 newMinimunFee); 13 | event StoppingChangingPercentage(); 14 | event StoppingChangingMinimunFee(); 15 | //state variables 16 | uint8 public percentage; 17 | uint256 public minimunFee; 18 | bool public cansetPercentage; 19 | bool public canChangeMinimunFee; 20 | //constructor 21 | function Taxable() public { 22 | cansetPercentage = true; 23 | canChangeMinimunFee = true; 24 | } 25 | /** 26 | * @dev change the contract percentage fee. 27 | * @param _percentage The new percentage. 28 | */ 29 | function setPercentage(uint8 _percentage) onlyOwner public returns(bool){ 30 | require(_percentage < 100); 31 | require(cansetPercentage); 32 | LogPercentageChanges(msg.sender,percentage,_percentage); 33 | percentage = _percentage; 34 | return true; 35 | } 36 | /** 37 | * @dev change the minimun fee. 38 | * @param _minimunFee The new minimun fee. 39 | */ 40 | function setMinimunFee(uint256 _minimunFee) onlyOwner public returns(bool){ 41 | require(canChangeMinimunFee); 42 | LogMinimunFeeChanges(msg.sender,minimunFee,_minimunFee); 43 | minimunFee = _minimunFee; 44 | return true; 45 | } 46 | /** 47 | * @dev make impossible to change percentage again 48 | */ 49 | function stopSetPercentage() public onlyOwner returns(bool){ 50 | require(cansetPercentage); 51 | cansetPercentage = false; 52 | StoppingChangingPercentage(); 53 | return true; 54 | } 55 | /** 56 | * @dev make impossible to change minimunFee again 57 | */ 58 | function stopSetMinimunFee() public onlyOwner returns(bool){ 59 | require(canChangeMinimunFee); 60 | canChangeMinimunFee = false; 61 | StoppingChangingMinimunFee(); 62 | return true; 63 | } 64 | } -------------------------------------------------------------------------------- /contracts/TaxableToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol"; 4 | import "./CappedToken.sol"; 5 | import "zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; 6 | import "./Taxable.sol"; 7 | import "./HasWhiteList.sol"; 8 | 9 | /** 10 | * @title ERC888 Token 11 | * @dev ERC20 Token with fee on sending 12 | */ 13 | contract TaxableToken is PausableToken,CappedToken,BurnableToken,Taxable,HasWhiteList{ 14 | //state variables 15 | string public name; 16 | string public symbol; 17 | uint8 public decimals; 18 | /** 19 | * @dev transfer token for a specified address burning a fee. 20 | * @param _to The address to transfer to. 21 | * @param _value The amount to be transferred. 22 | */ 23 | function transfer(address _to, uint256 _value) public returns (bool) { 24 | require(_to != address(0)); 25 | require(_value <= balances[msg.sender]); 26 | //required variables 27 | uint requiredPercentage; 28 | uint requiredMinimunFee; 29 | if(isInWhitelist[msg.sender]){ 30 | requiredPercentage = whitelistPercentage[msg.sender]; 31 | requiredMinimunFee = whitelistMinimunFee[msg.sender]; 32 | }else{ 33 | requiredPercentage = percentage; 34 | requiredMinimunFee = minimunFee; 35 | } 36 | require(_value > requiredMinimunFee); 37 | //expected fee 38 | uint fee = (_value * requiredPercentage)/100; 39 | //substraction 40 | balances[msg.sender] = balances[msg.sender].sub(_value); 41 | //check if the fee can be accepted 42 | if(fee < requiredMinimunFee){ 43 | totalSupply_ -= requiredMinimunFee; 44 | _value -= requiredMinimunFee; 45 | } 46 | else{ 47 | totalSupply_ -= fee; 48 | _value -= fee; 49 | } 50 | //transfer 51 | balances[_to] = balances[_to].add(_value); 52 | Transfer(msg.sender, _to, _value); 53 | return true; 54 | } 55 | /** 56 | * @dev Transfer tokens from one address to another burning a fee. 57 | * @param _from address The address which you want to send tokens from 58 | * @param _to address The address which you want to transfer to 59 | * @param _value uint256 the amount of tokens to be transferred 60 | */ 61 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 62 | require(_to != address(0)); 63 | require(_value <= balances[_from]); 64 | require(_value <= allowed[_from][msg.sender]); 65 | //required variables 66 | uint requiredPercentage; 67 | uint requiredMinimunFee; 68 | if(isInWhitelist[msg.sender]){ 69 | requiredPercentage = whitelistPercentage[msg.sender]; 70 | requiredMinimunFee = whitelistMinimunFee[msg.sender]; 71 | }else{ 72 | requiredPercentage = percentage; 73 | requiredMinimunFee = minimunFee; 74 | } 75 | require(_value > requiredMinimunFee); 76 | //expected fee 77 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); 78 | uint fee = (_value * requiredPercentage)/100; 79 | //substraction 80 | balances[_from] = balances[_from].sub(_value); 81 | //check if the fee can be accepted 82 | if(fee < requiredMinimunFee){ 83 | totalSupply_ -= requiredMinimunFee; 84 | _value -= requiredMinimunFee; 85 | } 86 | else{ 87 | totalSupply_ -= fee; 88 | _value -= fee; 89 | } 90 | //transfer 91 | balances[_to] = balances[_to].add(_value); 92 | Transfer(_from, _to, _value); 93 | return true; 94 | } 95 | } -------------------------------------------------------------------------------- /test/Taxable.js: -------------------------------------------------------------------------------- 1 | var assertRevert = require("../utils/assertRevert.js"); 2 | 3 | var Taxable = artifacts.require('Taxable'); 4 | 5 | contract('Taxable', function (accounts) { 6 | 7 | let taxable; 8 | const owner = accounts[0]; 9 | const anotherAccount = accounts[1]; 10 | 11 | beforeEach(async function () { 12 | taxable = await Taxable.new({from: owner}); 13 | }); 14 | 15 | it('should start with the correct state variables', async function () { 16 | let cansetPercentage = await taxable.cansetPercentage(); 17 | let canChangeMinimunFee = await taxable.canChangeMinimunFee(); 18 | assert(cansetPercentage); 19 | assert(canChangeMinimunFee); 20 | }); 21 | 22 | it('setPercentage should revert if it is not called from owner', async function () { 23 | await assertRevert(taxable.setPercentage(10,{from : anotherAccount})) 24 | }); 25 | 26 | it('setMinimunFee should revert if it is not called from owner', async function () { 27 | await assertRevert(taxable.setMinimunFee(100,{from : anotherAccount})) 28 | }); 29 | 30 | it('setPercentage should revert if the new percentage is 100 or higher', async function () { 31 | await assertRevert(taxable.setPercentage(100,{from : owner})) 32 | await assertRevert(taxable.setPercentage(101,{from : owner})) 33 | }); 34 | 35 | it('stopSetPercentage should revert if it is not called from owner', async function () { 36 | await assertRevert(taxable.stopSetPercentage({from : anotherAccount})) 37 | }); 38 | 39 | it('stopSetMinimunFee should revert if it is not called from owner', async function () { 40 | await assertRevert(taxable.stopSetMinimunFee({from : anotherAccount})) 41 | }); 42 | 43 | it('should not be able to stop change percentage twice', async function () { 44 | await taxable.stopSetPercentage({from : owner}); 45 | await assertRevert(taxable.stopSetPercentage({from : owner})) 46 | }); 47 | 48 | it('should not be able to stop change minimun fee twice', async function () { 49 | await taxable.stopSetMinimunFee({from : owner}); 50 | await assertRevert(taxable.stopSetMinimunFee({from : owner})) 51 | }); 52 | 53 | it('should not be able to change minimun fee after it stop change minimun fee', async function () { 54 | await taxable.stopSetMinimunFee({from : owner}); 55 | await assertRevert(taxable.setMinimunFee(10,{from : owner})) 56 | }); 57 | 58 | it('should not be able to change percentage after it stop change percentage', async function () { 59 | await taxable.stopSetPercentage({from : owner}); 60 | await assertRevert(taxable.setPercentage(10,{from : owner})) 61 | }); 62 | 63 | it('should be able to change percentage', async function () { 64 | await taxable.setPercentage(10,{from : owner}); 65 | let percentage = await taxable.percentage(); 66 | assert.equal(percentage,10); 67 | }); 68 | 69 | it('should be able to change the minimun fee', async function () { 70 | await taxable.setMinimunFee(10,{from : owner}); 71 | let fee = await taxable.minimunFee(); 72 | assert.equal(fee,10); 73 | }); 74 | 75 | it('setMinimunFee should return true', async function () { 76 | let result = await taxable.setMinimunFee(10,{from : owner}); 77 | assert(result); 78 | }); 79 | 80 | it('setPercentage should return true', async function () { 81 | let result = await taxable.setPercentage(10,{from : owner}); 82 | assert(result); 83 | }); 84 | 85 | it('stopSetPercentage should return true', async function () { 86 | let result = await taxable.stopSetPercentage({from : owner}); 87 | assert(result); 88 | }); 89 | 90 | it('stopSetMinimunFee should return true', async function () { 91 | let result = await taxable.stopSetMinimunFee({from : owner}); 92 | assert(result); 93 | }); 94 | }); -------------------------------------------------------------------------------- /contracts/HasWhiteList.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import "zeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | /* 5 | * @title HasWhiteList 6 | * @dev allow the contracts that inherit from this to have a whitelist 7 | */ 8 | contract HasWhiteList is Ownable{ 9 | //events 10 | event LogAddressAdded(address sender,address added); 11 | event LogAddressDeleted(address sender,address removed); 12 | event AddressMinimunFeeChanged(address sender,uint256 oldFee,uint256 newFee); 13 | event AddressPercentageChanged(address sender,uint256 oldPercentage,uint256 newPercentage); 14 | //mappings 15 | mapping (address => bool) isInWhitelist; 16 | mapping (address => uint8) whitelistPercentage; 17 | mapping (address => uint256) whitelistMinimunFee; 18 | /** 19 | * @dev check if a given account is in the whitelist 20 | * @param acc address to chek. 21 | */ 22 | function isInTheWhiteList(address acc) public constant returns(bool){ 23 | return isInWhitelist[acc]; 24 | } 25 | /** 26 | * @dev return the minimunFee assigned to a given account. 27 | * @param acc address to check. 28 | */ 29 | function getWhitelistedMinimunFee(address acc) public constant returns(uint256){ 30 | return whitelistMinimunFee[acc]; 31 | } 32 | /** 33 | * @dev return the percentage assigned to a given account. 34 | * @param acc address to chek. 35 | */ 36 | function getWhitelistedPercentage(address acc) public constant returns(uint256){ 37 | return whitelistPercentage[acc]; 38 | } 39 | /** 40 | * @dev add a new account in the whitelist. 41 | * @param acc address to add. 42 | * @param _percentage the percentage that has to be assigned to that account 43 | * @param _minimunFee the minimunFee that has to be assigned to that account 44 | */ 45 | function addWhitelistedAccount(address acc,uint8 _percentage,uint256 _minimunFee) onlyOwner public returns(bool){ 46 | require(!isInWhitelist[acc]); 47 | require(acc != 0); 48 | isInWhitelist[acc] = true; 49 | whitelistPercentage[acc] = _percentage; 50 | whitelistMinimunFee[acc] = _minimunFee; 51 | LogAddressAdded(msg.sender,acc); 52 | return true; 53 | } 54 | /** 55 | * @dev delete an account from the whitelist. 56 | * @param acc address to delete. 57 | */ 58 | function deleteWhitelistedAccount(address acc) onlyOwner public returns(bool){ 59 | require(isInWhitelist[acc]); 60 | isInWhitelist[acc] = false; 61 | whitelistPercentage[acc] = 0; 62 | whitelistMinimunFee[acc] = 0; 63 | LogAddressDeleted(msg.sender,acc); 64 | return true; 65 | } 66 | /** 67 | * @dev assign a new minimun fee to a whitelisted address; 68 | * @param acc address that have tobe modified. 69 | * @param amount the new minimun fee. 70 | */ 71 | function changeWhitelistedMinimunFee(address acc,uint256 amount) onlyOwner public returns(bool){ 72 | require(isInWhitelist[acc]); 73 | require(whitelistMinimunFee[acc] != amount); 74 | AddressMinimunFeeChanged(msg.sender,whitelistMinimunFee[acc],amount); 75 | whitelistMinimunFee[acc] = amount; 76 | return true; 77 | } 78 | /** 79 | * @dev assign a new percentage to a whitelisted address; 80 | * @param acc address that have to be modified. 81 | * @param amount the new percentage. 82 | */ 83 | function changeWhitelistedPercentage(address acc,uint8 amount) onlyOwner public returns(bool){ 84 | require(isInWhitelist[acc]); 85 | require(amount < 100); 86 | require(whitelistPercentage[acc] != amount); 87 | AddressPercentageChanged(msg.sender,whitelistPercentage[acc],amount); 88 | whitelistPercentage[acc] = amount; 89 | return true; 90 | } 91 | } -------------------------------------------------------------------------------- /test/HasWhiteList.js: -------------------------------------------------------------------------------- 1 | var assertRevert = require("../utils/assertRevert.js"); 2 | 3 | var HasWhiteList = artifacts.require('HasWhiteList'); 4 | 5 | contract('HasWhiteList', function (accounts) { 6 | 7 | let contract; 8 | const owner = accounts[0]; 9 | const anotherAccount = accounts[1]; 10 | 11 | beforeEach(async function () { 12 | contract = await HasWhiteList.new({from: owner}); 13 | }); 14 | 15 | it("should't add an account if sender is not the owner",async function(){ 16 | await assertRevert(contract.addWhitelistedAccount(owner,10,10,{from : anotherAccount})); 17 | }) 18 | 19 | it("should't remove an account if it's not in the whitelists",async function(){ 20 | await assertRevert(contract.deleteWhitelistedAccount(owner,{from : owner})); 21 | }) 22 | 23 | it("shouldn't remove an account if sender is not the owner",async function(){ 24 | await contract.addWhitelistedAccount(owner,10,10,{from : owner}); 25 | await assertRevert(contract.deleteWhitelistedAccount(owner,{from : anotherAccount})); 26 | }) 27 | 28 | it("shouldn't add an account if that account already exist",async function(){ 29 | await contract.addWhitelistedAccount(owner,10,10,{from : owner}); 30 | await assertRevert(contract.addWhitelistedAccount(owner,10,10,{from : owner})); 31 | }) 32 | 33 | it("shouldn't add an account if that account is 0x0",async function(){ 34 | await assertRevert(contract.addWhitelistedAccount('0x0',10,10,{from : owner})); 35 | }) 36 | 37 | it("should add an account and delete an account",async function(){ 38 | await contract.addWhitelistedAccount(owner,10,15,{from : owner}); 39 | let isIn = await contract.isInTheWhiteList(owner); 40 | let minimunFee = await contract.getWhitelistedMinimunFee(owner); 41 | let percentage = await contract.getWhitelistedPercentage(owner); 42 | assert(isIn); 43 | assert.equal(minimunFee,15); 44 | assert.equal(percentage,10); 45 | await contract.deleteWhitelistedAccount(owner,{from : owner}); 46 | isIn = await contract.isInTheWhiteList(owner); 47 | minimunFee = await contract.getWhitelistedMinimunFee(owner); 48 | percentage = await contract.getWhitelistedPercentage(owner); 49 | assert(!isIn); 50 | assert.equal(percentage,0); 51 | assert.equal(minimunFee,0); 52 | }) 53 | 54 | it("shouldn't modify minimunFee if the sender is not the ownner",async function(){ 55 | await contract.addWhitelistedAccount(owner,10,10,{from : owner}); 56 | await assertRevert(contract.changeWhitelistedMinimunFee(owner,20,{from : anotherAccount})); 57 | }) 58 | 59 | it("shouldn't modify percentage if the sender is not the ownner",async function(){ 60 | await contract.addWhitelistedAccount(owner,10,10,{from : owner}); 61 | await assertRevert(contract.changeWhitelistedPercentage(owner,20,{from : anotherAccount})); 62 | }) 63 | 64 | it("shouldn't modify minimunFee if the account isn't in whitelist",async function(){ 65 | await assertRevert(contract.changeWhitelistedMinimunFee(owner,20,{from : owner})); 66 | }) 67 | 68 | it("shouldn't modify percentage if the account isn't in whitelist",async function(){ 69 | await assertRevert(contract.changeWhitelistedPercentage(owner,20,{from : owner})); 70 | }) 71 | 72 | it("shouldn't modify percentage if the new percentage is 100 or higher",async function(){ 73 | await contract.addWhitelistedAccount(owner,10,10,{from : owner}); 74 | await assertRevert(contract.changeWhitelistedPercentage(owner,100,{from : owner})); 75 | await assertRevert(contract.changeWhitelistedPercentage(owner,101,{from : owner})); 76 | }) 77 | 78 | it("shouldn't modify percentage if the new percentage is equal to the old one",async function(){ 79 | await contract.addWhitelistedAccount(owner,0,0,{from : owner}); 80 | await contract.changeWhitelistedPercentage(owner,10,{from : owner}); 81 | await assertRevert(contract.changeWhitelistedPercentage(owner,10,{from : owner})); 82 | }) 83 | 84 | it("shouldn't modify minimunFee if the new fee is equal to the old one",async function(){ 85 | await contract.addWhitelistedAccount(owner,0,0,{from : owner}); 86 | await contract.changeWhitelistedMinimunFee(owner,10,{from : owner}); 87 | await assertRevert(contract.changeWhitelistedMinimunFee(owner,10,{from : owner})); 88 | }) 89 | 90 | it("should modify percentage and minimunFee",async function(){ 91 | await contract.addWhitelistedAccount(owner,0,0,{from : owner}); 92 | await contract.changeWhitelistedPercentage(owner,10,{from : owner}); 93 | let result = await contract.getWhitelistedPercentage(owner); 94 | assert.equal(result,10); 95 | await contract.changeWhitelistedMinimunFee(owner,10,{from : owner}); 96 | result = await contract.getWhitelistedMinimunFee(owner); 97 | assert.equal(result,10); 98 | }) 99 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Implementation. 2 | 3 | ## Current implementation 4 | 5 | This repo's contracts are separated in 3 parts: 6 | 7 | - [Taxable](https://github.com/justshiftjk/ERC20-Tax-Token-Contract/blob/master/contracts/Taxable.sol): have state variables used to make the contract taxable. 8 | - [Whitelist](https://github.com/justshiftjk/ERC20-Tax-Token-Contract/blob/master/contracts/HasWhiteList.sol): Allow the owner to add custom fees rules for certain accounts. 9 | - [The token itself](https://github.com/justshiftjk/ERC20-Tax-Token-Contract/blob/master/contracts/TaxableToken.sol): ERC20 with a fee in percentage on sending. 10 | 11 | ## Minimal viable implementation of the token, ready for use. 12 | 13 | https://github.com/Giulio2002/Ethereum-Taxable-Token 14 | 15 | ## Taxable Token. 16 | 17 | Ethereum-Taxable-Token is based on [ERC20](https://github.com/OpenZeppelin/zeppelin-solidity/tree/master/contracts/token/ERC20) token standard. It should be used to create token that you can tax as you wish. 18 | 19 | ```js 20 | pragma solidity ^0.4.18; 21 | 22 | import "zeppelin-solidity/contracts/token/ERC20/PausableToken.sol"; 23 | import "./CappedToken.sol"; 24 | import "zeppelin-solidity/contracts/token/ERC20/BurnableToken.sol"; 25 | import "./Taxable.sol"; 26 | import "./HasWhiteList.sol"; 27 | 28 | /** 29 | * @title ERC888 Token 30 | * @dev ERC20 Token with fee on sending 31 | */ 32 | contract TaxableToken is PausableToken,CappedToken,BurnableToken,Taxable,HasWhiteList{ 33 | //state variables 34 | string public name; 35 | string public symbol; 36 | uint8 public decimals; 37 | /** 38 | * @dev transfer token for a specified address burning a fee. 39 | * @param _to The address to transfer to. 40 | * @param _value The amount to be transferred. 41 | */ 42 | function transfer(address _to, uint256 _value) public returns (bool) { 43 | require(_to != address(0)); 44 | require(_value <= balances[msg.sender]); 45 | //required variables 46 | uint requiredPercentage; 47 | uint requiredMinimunFee; 48 | if(isInWhitelist[msg.sender]){ 49 | requiredPercentage = whitelistPercentage[msg.sender]; 50 | requiredMinimunFee = whitelistMinimunFee[msg.sender]; 51 | }else{ 52 | requiredPercentage = percentage; 53 | requiredMinimunFee = minimunFee; 54 | } 55 | require(_value > requiredMinimunFee); 56 | //expected fee 57 | uint fee = (_value * requiredPercentage)/100; 58 | //substraction 59 | balances[msg.sender] = balances[msg.sender].sub(_value); 60 | //check if the fee can be accepted 61 | if(fee < requiredMinimunFee){ 62 | totalSupply_ -= requiredMinimunFee; 63 | _value -= requiredMinimunFee; 64 | } 65 | else{ 66 | totalSupply_ -= fee; 67 | _value -= fee; 68 | } 69 | //transfer 70 | balances[_to] = balances[_to].add(_value); 71 | Transfer(msg.sender, _to, _value); 72 | return true; 73 | } 74 | /** 75 | * @dev Transfer tokens from one address to another burning a fee. 76 | * @param _from address The address which you want to send tokens from 77 | * @param _to address The address which you want to transfer to 78 | * @param _value uint256 the amount of tokens to be transferred 79 | */ 80 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 81 | require(_to != address(0)); 82 | require(_value <= balances[_from]); 83 | require(_value <= allowed[_from][msg.sender]); 84 | //required variables 85 | uint requiredPercentage; 86 | uint requiredMinimunFee; 87 | if(isInWhitelist[msg.sender]){ 88 | requiredPercentage = whitelistPercentage[msg.sender]; 89 | requiredMinimunFee = whitelistMinimunFee[msg.sender]; 90 | }else{ 91 | requiredPercentage = percentage; 92 | requiredMinimunFee = minimunFee; 93 | } 94 | require(_value > requiredMinimunFee); 95 | //expected fee 96 | allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); 97 | uint fee = (_value * requiredPercentage)/100; 98 | //substraction 99 | balances[_from] = balances[_from].sub(_value); 100 | //check if the fee can be accepted 101 | if(fee < requiredMinimunFee){ 102 | totalSupply_ -= requiredMinimunFee; 103 | _value -= requiredMinimunFee; 104 | } 105 | else{ 106 | totalSupply_ -= fee; 107 | _value -= fee; 108 | } 109 | //transfer 110 | balances[_to] = balances[_to].add(_value); 111 | Transfer(_from, _to, _value); 112 | return true; 113 | } 114 | } 115 | ``` 116 | 117 | ## Taxable Token disadvantages. 118 | 1. cost a lot of gas for deploying(4000000 gas on kovan) 119 | 2. cost more gas in the sending operation(around 58000) than a normal ERC20(around 50000). 120 | 121 | ## Example of a possible contract created using this 'standard' 122 | 123 | ```js 124 | pragma solidity ^0.4.18; 125 | 126 | import "./TaxableToken.sol"; 127 | 128 | contract TaxableTokenMock is TaxableToken{ 129 | function TaxableTokenMock(uint256 supply, uint256 _minimunFee, uint8 _percentage,string _name,string _symbol,uint8 _decimals) public{ 130 | require(_percentage < 100); 131 | require(supply > 0); 132 | require(_decimals < 18); 133 | name = _name; 134 | symbol = _symbol; 135 | decimals = _decimals; 136 | minimunFee = _minimunFee; 137 | percentage = _percentage; 138 | totalSupply_ = supply; 139 | cap = supply; 140 | balances[msg.sender] = supply; 141 | } 142 | } 143 | ``` 144 | ## Info 145 | * you can find a deployed instance of this token at https://kovan.etherscan.io/address/0x3515472ab191e10e5e8020b5e9745e727b338120 146 | ## How to use it 147 | * download this repo 148 | * import this repo's contracts in your contracts folder. 149 | * try with the example token 150 | 151 | ## Purpose 152 | The ico's popularity have caused the creation of new economics standards for the sustainability and the increasing of the price of the emitted tokens 153 | 154 | Generally the price of a token is based on one of those two concepts: 155 | 156 | * Network Effect 157 | * Deflation using the burning 158 | 159 | The idea is simple,we apply a tax to the sender when an amount of tokens are sent, usually in percentage. 160 | 161 | ### Example: 162 | 163 | The Taxable token apply a tax to each transaction, so if A transfers N tokens to B, B receive N – t(N) tokens, where t(N) is a tax calculated usually in percentage. 164 | 165 | #### The purpose can be: 166 | * gaining using the tax 167 | * deflationing the token using the burning so that the token become more expensive 168 | 169 | ## Methods 170 | 171 | * Set Percentage: define the percentage tax 172 | * Set Minimum Fee: define the minimun fee that an user can pay 173 | * AddWhitelistedAccount: make some selected accounts pay a different tax 174 | * ChangeWhitelistedPercentage: redefine the percentage for a whitelisted account 175 | * ChangeWhitelistedMinimumFee: redefine the minimun fee that a whitelisted account have to pay 176 | * DeleteWhitelistedAccount: delete an account from the whitelist 177 | ## how it works 178 | * A send 10 token to B 179 | * C has set the percentage to 10% 180 | * B receive 9 token,the one that miss is burned 181 | * C(the owner) can mint while the maximum supply is not reached 182 | * the owner can't mint more than the maximum supply 183 | ### Why C haven't received the token when A have sent his 10 tokes to B 184 | I've realized that sending the token to C would have cost a big amount of gas to A,so: 185 | 186 | I replaced this operation with a burning(that cost a lot less),then i decided to make the owner mint the amount of token that had been burned. 187 | 188 | acting that way results less expensive for the sender. 189 | 190 | ##### Remember: the owner can't mint more than the maximum supply 191 | -------------------------------------------------------------------------------- /test/TaxableToken.js: -------------------------------------------------------------------------------- 1 | var assertRevert = require("../utils/assertRevert.js"); 2 | const TaxableToken = artifacts.require('TaxableTokenMock'); 3 | 4 | contract('TaxableToken', function ([_, owner, recipient, anotherAccount]) { 5 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 6 | 7 | beforeEach(async function () { 8 | this.token = await TaxableToken.new(100,0,10,"token","tkn",10,{from : owner}); 9 | }); 10 | 11 | describe('total supply', function () { 12 | it('returns the total amount of tokens', async function () { 13 | const totalSupply = await this.token.totalSupply(); 14 | 15 | assert.equal(totalSupply, 100); 16 | }); 17 | }); 18 | 19 | describe('balanceOf', function () { 20 | describe('when the requested account has no tokens', function () { 21 | it('returns zero', async function () { 22 | const balance = await this.token.balanceOf(anotherAccount); 23 | 24 | assert.equal(balance, 0); 25 | }); 26 | }); 27 | 28 | describe('when the requested account has some tokens', function () { 29 | it('returns the total amount of tokens', async function () { 30 | const balance = await this.token.balanceOf(owner); 31 | 32 | assert.equal(balance, 100); 33 | }); 34 | }); 35 | }); 36 | 37 | describe('transfer', function () { 38 | describe('when the recipient is not the zero address', function () { 39 | const to = recipient; 40 | 41 | describe('when the sender does not have enough balance', function () { 42 | const amount = 101; 43 | 44 | it('reverts', async function () { 45 | await assertRevert(this.token.transfer(to, amount, { from: owner })); 46 | }); 47 | it('reverts', async function () { 48 | await this.token.setMinimunFee(50, { from: owner }); 49 | await assertRevert(this.token.transfer(to, amount - 70, { from: owner })); 50 | }); 51 | 52 | it('reverts', async function () { 53 | await this.token.addWhitelistedAccount(owner,0,50,{from : owner}); 54 | await assertRevert(this.token.transfer(to, amount - 70, { from: owner })); 55 | }); 56 | }); 57 | 58 | describe('when the sender has enough balance', function () { 59 | const amount = 50; 60 | 61 | it('transfers the requested amount using percentage', async function () { 62 | await this.token.transfer(to, amount, { from: owner }); 63 | 64 | const senderBalance = await this.token.balanceOf(owner); 65 | assert.equal(senderBalance, 50); 66 | 67 | const recipientBalance = await this.token.balanceOf(to); 68 | assert.equal(recipientBalance.c[0], amount - 5 ); 69 | //should be able to mint the fee 70 | await this.token.mint(owner,5,{from : owner}); 71 | }); 72 | 73 | it('transfers the requested amount using minimun fee', async function () { 74 | await this.token.setMinimunFee(40, { from: owner }); 75 | await this.token.transfer(to, amount, { from: owner }); 76 | 77 | const senderBalance = await this.token.balanceOf(owner); 78 | assert.equal(senderBalance, 50); 79 | 80 | const recipientBalance = await this.token.balanceOf(to); 81 | assert.equal(recipientBalance.c[0], amount - 40); 82 | //should be able to mint the fee 83 | await this.token.mint(owner,40,{from : owner}); 84 | }); 85 | 86 | it('transfers the requested amount using whitelisted percentage', async function () { 87 | await this.token.addWhitelistedAccount(owner,20,0,{from : owner}); 88 | await this.token.transfer(to, amount, { from: owner }); 89 | const senderBalance = await this.token.balanceOf(owner); 90 | assert.equal(senderBalance, 50); 91 | 92 | const recipientBalance = await this.token.balanceOf(to); 93 | assert.equal(recipientBalance.c[0], amount - 10 ); 94 | //should be able to mint the fee 95 | await this.token.mint(owner,10,{from : owner}); 96 | }); 97 | 98 | it('transfers the requested amount using whitelisted minimun fee', async function () { 99 | await this.token.addWhitelistedAccount(owner,0,40,{from : owner}); 100 | await this.token.transfer(to, amount, { from: owner }); 101 | 102 | const senderBalance = await this.token.balanceOf(owner); 103 | assert.equal(senderBalance, 50); 104 | 105 | const recipientBalance = await this.token.balanceOf(to); 106 | assert.equal(recipientBalance.c[0], amount - 40); 107 | //should be able to mint the fee 108 | await this.token.mint(owner,40,{from : owner}); 109 | }); 110 | 111 | it('emits a transfer event', async function () { 112 | const { logs } = await this.token.transfer(to, amount, { from: owner }); 113 | 114 | assert.equal(logs.length, 1); 115 | assert.equal(logs[0].event, 'Transfer'); 116 | assert.equal(logs[0].args.from, owner); 117 | assert.equal(logs[0].args.to, to); 118 | assert(logs[0].args.value.eq(45)); 119 | }); 120 | }); 121 | }); 122 | 123 | describe('when the recipient is the zero address', function () { 124 | const to = ZERO_ADDRESS; 125 | 126 | it('reverts', async function () { 127 | await assertRevert(this.token.transfer(to, 100, { from: owner })); 128 | }); 129 | }); 130 | }); 131 | describe('approve', function () { 132 | describe('when the spender is not the zero address', function () { 133 | const spender = recipient; 134 | 135 | describe('when the sender has enough balance', function () { 136 | const amount = 100; 137 | 138 | it('emits an approval event', async function () { 139 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 140 | 141 | assert.equal(logs.length, 1); 142 | assert.equal(logs[0].event, 'Approval'); 143 | assert.equal(logs[0].args.owner, owner); 144 | assert.equal(logs[0].args.spender, spender); 145 | assert(logs[0].args.value.eq(amount)); 146 | }); 147 | 148 | describe('when there was no approved amount before', function () { 149 | it('approves the requested amount', async function () { 150 | await this.token.approve(spender, amount, { from: owner }); 151 | 152 | const allowance = await this.token.allowance(owner, spender); 153 | assert.equal(allowance, amount); 154 | }); 155 | }); 156 | 157 | describe('when the spender had an approved amount', function () { 158 | beforeEach(async function () { 159 | await this.token.approve(spender, 1, { from: owner }); 160 | }); 161 | 162 | it('approves the requested amount and replaces the previous one', async function () { 163 | await this.token.approve(spender, amount, { from: owner }); 164 | 165 | const allowance = await this.token.allowance(owner, spender); 166 | assert.equal(allowance, amount); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('when the sender does not have enough balance', function () { 172 | const amount = 101; 173 | 174 | it('emits an approval event', async function () { 175 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 176 | 177 | assert.equal(logs.length, 1); 178 | assert.equal(logs[0].event, 'Approval'); 179 | assert.equal(logs[0].args.owner, owner); 180 | assert.equal(logs[0].args.spender, spender); 181 | assert(logs[0].args.value.eq(amount)); 182 | }); 183 | 184 | describe('when there was no approved amount before', function () { 185 | it('approves the requested amount', async function () { 186 | await this.token.approve(spender, amount, { from: owner }); 187 | 188 | const allowance = await this.token.allowance(owner, spender); 189 | assert.equal(allowance, amount); 190 | }); 191 | }); 192 | 193 | describe('when the spender had an approved amount', function () { 194 | beforeEach(async function () { 195 | await this.token.approve(spender, 1, { from: owner }); 196 | }); 197 | 198 | it('approves the requested amount and replaces the previous one', async function () { 199 | await this.token.approve(spender, amount, { from: owner }); 200 | 201 | const allowance = await this.token.allowance(owner, spender); 202 | assert.equal(allowance, amount); 203 | }); 204 | }); 205 | }); 206 | }); 207 | 208 | describe('when the spender is the zero address', function () { 209 | const amount = 100; 210 | const spender = ZERO_ADDRESS; 211 | 212 | it('approves the requested amount', async function () { 213 | await this.token.approve(spender, amount, { from: owner }); 214 | 215 | const allowance = await this.token.allowance(owner, spender); 216 | assert.equal(allowance, amount); 217 | }); 218 | 219 | it('emits an approval event', async function () { 220 | const { logs } = await this.token.approve(spender, amount, { from: owner }); 221 | 222 | assert.equal(logs.length, 1); 223 | assert.equal(logs[0].event, 'Approval'); 224 | assert.equal(logs[0].args.owner, owner); 225 | assert.equal(logs[0].args.spender, spender); 226 | assert(logs[0].args.value.eq(amount)); 227 | }); 228 | }); 229 | }); 230 | 231 | describe('transfer from', function () { 232 | const spender = recipient; 233 | 234 | describe('when the recipient is not the zero address', function () { 235 | const to = anotherAccount; 236 | 237 | describe('when the spender has enough approved balance', function () { 238 | beforeEach(async function () { 239 | await this.token.approve(spender, 100, { from: owner }); 240 | }); 241 | 242 | describe('when the owner has enough balance', function () { 243 | const amount = 50; 244 | 245 | it('transfers the requested amount using percentage', async function () { 246 | await this.token.transferFrom(owner, to, amount, { from: spender }); 247 | 248 | const senderBalance = await this.token.balanceOf(owner); 249 | assert.equal(senderBalance, 50); 250 | 251 | const recipientBalance = await this.token.balanceOf(to); 252 | assert.equal(recipientBalance, amount-5); 253 | //should be able to mint the fee 254 | await this.token.mint(owner,5,{from : owner}); 255 | }); 256 | 257 | it('transfers the requested amount using minimun fee', async function () { 258 | await this.token.setMinimunFee(40,{from : owner}) 259 | await this.token.transferFrom(owner, to, amount, { from: spender }); 260 | 261 | const senderBalance = await this.token.balanceOf(owner); 262 | assert.equal(senderBalance, 50); 263 | 264 | const recipientBalance = await this.token.balanceOf(to); 265 | assert.equal(recipientBalance, amount-40); 266 | }); 267 | 268 | it('transfers the requested amount using whitelisted percentage', async function () { 269 | await this.token.addWhitelistedAccount(spender,20,0,{from : owner}) 270 | await this.token.transferFrom(owner, to, amount, { from: spender }); 271 | 272 | const senderBalance = await this.token.balanceOf(owner); 273 | assert.equal(senderBalance, 50); 274 | 275 | const recipientBalance = await this.token.balanceOf(to); 276 | assert.equal(recipientBalance.c[0], amount-10); 277 | //should be able to mint the fee 278 | await this.token.mint(owner,10,{from : owner}); 279 | }); 280 | 281 | it('transfers the requested amount using whitelisted minimun fee', async function () { 282 | await this.token.addWhitelistedAccount(spender,0,20,{from : owner}) 283 | await this.token.transferFrom(owner, to, amount, { from: spender }); 284 | 285 | const senderBalance = await this.token.balanceOf(owner); 286 | assert.equal(senderBalance.c[0], 50); 287 | 288 | const recipientBalance = await this.token.balanceOf(to); 289 | assert.equal(recipientBalance.c[0], amount-20); 290 | //should be able to mint the fee 291 | await this.token.mint(owner,20,{from : owner}); 292 | }); 293 | 294 | it('decreases the spender allowance', async function () { 295 | await this.token.transferFrom(owner, to, amount, { from: spender }); 296 | 297 | const allowance = await this.token.allowance(owner, spender); 298 | assert(allowance.eq(50)); 299 | }); 300 | 301 | it('emits a transfer event', async function () { 302 | const { logs } = await this.token.transferFrom(owner, to, amount, { from: spender }); 303 | 304 | assert.equal(logs.length, 1); 305 | assert.equal(logs[0].event, 'Transfer'); 306 | assert.equal(logs[0].args.from, owner); 307 | assert.equal(logs[0].args.to, to); 308 | assert(logs[0].args.value.eq(amount-5)); 309 | }); 310 | }); 311 | 312 | describe('when the owner does not have enough balance', function () { 313 | const amount = 101; 314 | 315 | it('reverts', async function () { 316 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 317 | }); 318 | 319 | it('reverts', async function () { 320 | await this.token.addWhitelistedAccount(spender,0,20,{from : owner}) 321 | await assertRevert(this.token.transferFrom(owner, to, 10, { from: spender })); 322 | }); 323 | 324 | it('reverts', async function () { 325 | await this.token.setMinimunFee(100,{from : owner}) 326 | await assertRevert(this.token.transferFrom(owner, to, 0, { from: spender })); 327 | }); 328 | }); 329 | }); 330 | 331 | describe('when the spender does not have enough approved balance', function () { 332 | beforeEach(async function () { 333 | await this.token.approve(spender, 99, { from: owner }); 334 | }); 335 | 336 | describe('when the owner has enough balance', function () { 337 | const amount = 100; 338 | 339 | it('reverts', async function () { 340 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 341 | }); 342 | }); 343 | 344 | describe('when the owner does not have enough balance', function () { 345 | const amount = 101; 346 | 347 | it('reverts', async function () { 348 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 349 | }); 350 | }); 351 | }); 352 | }); 353 | 354 | describe('when the recipient is the zero address', function () { 355 | const amount = 100; 356 | const to = ZERO_ADDRESS; 357 | 358 | beforeEach(async function () { 359 | await this.token.approve(spender, amount, { from: owner }); 360 | }); 361 | 362 | it('reverts', async function () { 363 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: spender })); 364 | }); 365 | }); 366 | }); 367 | 368 | describe('decrease approval', function () { 369 | describe('when the spender is not the zero address', function () { 370 | const spender = recipient; 371 | 372 | describe('when the sender has enough balance', function () { 373 | const amount = 100; 374 | 375 | it('emits an approval event', async function () { 376 | const { logs } = await this.token.decreaseApproval(spender, amount, { from: owner }); 377 | 378 | assert.equal(logs.length, 1); 379 | assert.equal(logs[0].event, 'Approval'); 380 | assert.equal(logs[0].args.owner, owner); 381 | assert.equal(logs[0].args.spender, spender); 382 | assert(logs[0].args.value.eq(0)); 383 | }); 384 | 385 | describe('when there was no approved amount before', function () { 386 | it('keeps the allowance to zero', async function () { 387 | await this.token.decreaseApproval(spender, amount, { from: owner }); 388 | 389 | const allowance = await this.token.allowance(owner, spender); 390 | assert.equal(allowance, 0); 391 | }); 392 | }); 393 | 394 | describe('when the spender had an approved amount', function () { 395 | beforeEach(async function () { 396 | await this.token.approve(spender, amount + 1, { from: owner }); 397 | }); 398 | 399 | it('decreases the spender allowance subtracting the requested amount', async function () { 400 | await this.token.decreaseApproval(spender, amount, { from: owner }); 401 | 402 | const allowance = await this.token.allowance(owner, spender); 403 | assert.equal(allowance, 1); 404 | }); 405 | }); 406 | }); 407 | 408 | describe('when the sender does not have enough balance', function () { 409 | const amount = 101; 410 | 411 | it('emits an approval event', async function () { 412 | const { logs } = await this.token.decreaseApproval(spender, amount, { from: owner }); 413 | 414 | assert.equal(logs.length, 1); 415 | assert.equal(logs[0].event, 'Approval'); 416 | assert.equal(logs[0].args.owner, owner); 417 | assert.equal(logs[0].args.spender, spender); 418 | assert(logs[0].args.value.eq(0)); 419 | }); 420 | 421 | describe('when there was no approved amount before', function () { 422 | it('keeps the allowance to zero', async function () { 423 | await this.token.decreaseApproval(spender, amount, { from: owner }); 424 | 425 | const allowance = await this.token.allowance(owner, spender); 426 | assert.equal(allowance, 0); 427 | }); 428 | }); 429 | 430 | describe('when the spender had an approved amount', function () { 431 | beforeEach(async function () { 432 | await this.token.approve(spender, amount + 1, { from: owner }); 433 | }); 434 | 435 | it('decreases the spender allowance subtracting the requested amount', async function () { 436 | await this.token.decreaseApproval(spender, amount, { from: owner }); 437 | 438 | const allowance = await this.token.allowance(owner, spender); 439 | assert.equal(allowance, 1); 440 | }); 441 | }); 442 | }); 443 | }); 444 | 445 | describe('when the spender is the zero address', function () { 446 | const amount = 100; 447 | const spender = ZERO_ADDRESS; 448 | 449 | it('decreases the requested amount', async function () { 450 | await this.token.decreaseApproval(spender, amount, { from: owner }); 451 | 452 | const allowance = await this.token.allowance(owner, spender); 453 | assert.equal(allowance, 0); 454 | }); 455 | 456 | it('emits an approval event', async function () { 457 | const { logs } = await this.token.decreaseApproval(spender, amount, { from: owner }); 458 | 459 | assert.equal(logs.length, 1); 460 | assert.equal(logs[0].event, 'Approval'); 461 | assert.equal(logs[0].args.owner, owner); 462 | assert.equal(logs[0].args.spender, spender); 463 | assert(logs[0].args.value.eq(0)); 464 | }); 465 | }); 466 | }); 467 | 468 | describe('increase approval', function () { 469 | const amount = 100; 470 | 471 | describe('when the spender is not the zero address', function () { 472 | const spender = recipient; 473 | 474 | describe('when the sender has enough balance', function () { 475 | it('emits an approval event', async function () { 476 | const { logs } = await this.token.increaseApproval(spender, amount, { from: owner }); 477 | 478 | assert.equal(logs.length, 1); 479 | assert.equal(logs[0].event, 'Approval'); 480 | assert.equal(logs[0].args.owner, owner); 481 | assert.equal(logs[0].args.spender, spender); 482 | assert(logs[0].args.value.eq(amount)); 483 | }); 484 | 485 | describe('when there was no approved amount before', function () { 486 | it('approves the requested amount', async function () { 487 | await this.token.increaseApproval(spender, amount, { from: owner }); 488 | 489 | const allowance = await this.token.allowance(owner, spender); 490 | assert.equal(allowance, amount); 491 | }); 492 | }); 493 | 494 | describe('when the spender had an approved amount', function () { 495 | beforeEach(async function () { 496 | await this.token.approve(spender, 1, { from: owner }); 497 | }); 498 | 499 | it('increases the spender allowance adding the requested amount', async function () { 500 | await this.token.increaseApproval(spender, amount, { from: owner }); 501 | 502 | const allowance = await this.token.allowance(owner, spender); 503 | assert.equal(allowance, amount + 1); 504 | }); 505 | }); 506 | }); 507 | 508 | describe('when the sender does not have enough balance', function () { 509 | const amount = 101; 510 | 511 | it('emits an approval event', async function () { 512 | const { logs } = await this.token.increaseApproval(spender, amount, { from: owner }); 513 | 514 | assert.equal(logs.length, 1); 515 | assert.equal(logs[0].event, 'Approval'); 516 | assert.equal(logs[0].args.owner, owner); 517 | assert.equal(logs[0].args.spender, spender); 518 | assert(logs[0].args.value.eq(amount)); 519 | }); 520 | 521 | describe('when there was no approved amount before', function () { 522 | it('approves the requested amount', async function () { 523 | await this.token.increaseApproval(spender, amount, { from: owner }); 524 | 525 | const allowance = await this.token.allowance(owner, spender); 526 | assert.equal(allowance, amount); 527 | }); 528 | }); 529 | 530 | describe('when the spender had an approved amount', function () { 531 | beforeEach(async function () { 532 | await this.token.approve(spender, 1, { from: owner }); 533 | }); 534 | 535 | it('increases the spender allowance adding the requested amount', async function () { 536 | await this.token.increaseApproval(spender, amount, { from: owner }); 537 | 538 | const allowance = await this.token.allowance(owner, spender); 539 | assert.equal(allowance, amount + 1); 540 | }); 541 | }); 542 | }); 543 | }); 544 | 545 | describe('when the spender is the zero address', function () { 546 | const spender = ZERO_ADDRESS; 547 | 548 | it('approves the requested amount', async function () { 549 | await this.token.increaseApproval(spender, amount, { from: owner }); 550 | 551 | const allowance = await this.token.allowance(owner, spender); 552 | assert.equal(allowance, amount); 553 | }); 554 | 555 | it('emits an approval event', async function () { 556 | const { logs } = await this.token.increaseApproval(spender, amount, { from: owner }); 557 | 558 | assert.equal(logs.length, 1); 559 | assert.equal(logs[0].event, 'Approval'); 560 | assert.equal(logs[0].args.owner, owner); 561 | assert.equal(logs[0].args.spender, spender); 562 | assert(logs[0].args.value.eq(amount)); 563 | }); 564 | }); 565 | }); 566 | }); 567 | --------------------------------------------------------------------------------