├── migrations └── .gitkeep ├── .eslintignore ├── .soliumignore ├── .gitattributes ├── .gitignore ├── test ├── helpers │ ├── EVMRevert.js │ ├── assertRevert.js │ ├── assertJump.js │ └── expectEvent.js ├── TokenBlackHole.test.js ├── Ownable.test.js ├── SafeERC20.test.js ├── BurnOwner.test.js ├── BasicToken.test.js ├── SafeMath.test.js ├── MintableToken.test.js ├── TokenBatchTransfer.test.js ├── CandyToken.test.js ├── FreezeableToken.test.js ├── StandardToken.test.js └── DemoToken.test.js ├── .babelrc ├── logo.png ├── scripts ├── coverage.sh └── test.sh ├── .solcover.js ├── contracts ├── mocks │ ├── BasicTokenMock.sol │ ├── StandardTokenMock.sol │ ├── BurnOwnerTokenMock.sol │ ├── FreezeableTokenMock.sol │ ├── CandyTokenMock.sol │ ├── SafeMathMock.sol │ └── SafeERC20Mock.sol ├── ERC20Basic.sol ├── TokenBlackHole.sol ├── ERC20.sol ├── demos │ └── DemoToken.sol ├── libs │ ├── Math.sol │ └── SafeMath.sol ├── BurnOwnerToken.sol ├── SafeERC20.sol ├── Ownable.sol ├── MintableToken.sol ├── BasicToken.sol ├── Freezeable.sol ├── FreezeableToken.sol ├── extends │ └── TokenBatchTransfer.sol ├── StandardToken.sol └── CandyToken.sol ├── .soliumrc.json ├── truffle-config.js ├── .travis.yml ├── LICENSE ├── .eslintrc ├── package.json └── README.md /migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | scTopics 2 | coverage 3 | coverage.json 4 | -------------------------------------------------------------------------------- /test/helpers/EVMRevert.js: -------------------------------------------------------------------------------- 1 | export default 'revert'; 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "stage-3"] 3 | } 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/armors/armors-solidity/HEAD/logo.png -------------------------------------------------------------------------------- /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOLIDITY_COVERAGE=true scripts/test.sh 4 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | norpc: true, 3 | testCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle test --network coverage', 4 | compileCommand: 'node --max-old-space-size=4096 ../node_modules/.bin/truffle compile --network coverage', 5 | skipFiles: [ 6 | 'mocks' 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/helpers/assertRevert.js: -------------------------------------------------------------------------------- 1 | export default async 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 | -------------------------------------------------------------------------------- /contracts/mocks/BasicTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../BasicToken.sol"; 4 | 5 | 6 | contract BasicTokenMock is BasicToken { 7 | 8 | constructor(address initialAccount, uint256 initialBalance) public { 9 | _balances[initialAccount] = initialBalance; 10 | _totalSupply = initialBalance; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mocks/StandardTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../StandardToken.sol"; 4 | 5 | 6 | contract StandardTokenMock is StandardToken { 7 | 8 | constructor(address initialAccount, uint256 initialBalance) public { 9 | _balances[initialAccount] = initialBalance; 10 | _totalSupply = initialBalance; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mocks/BurnOwnerTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../BurnOwnerToken.sol"; 4 | 5 | 6 | contract BurnOwnerTokenMock is BurnOwnerToken { 7 | 8 | constructor(address initialAccount, uint256 initialBalance) public { 9 | _balances[initialAccount] = initialBalance; 10 | _totalSupply = initialBalance; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /contracts/mocks/FreezeableTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../FreezeableToken.sol"; 4 | 5 | 6 | contract FreezeableTokenMock is FreezeableToken { 7 | 8 | constructor(address initialAccount, uint256 initialBalance) public { 9 | _balances[initialAccount] = initialBalance; 10 | _totalSupply = initialBalance; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /test/helpers/assertJump.js: -------------------------------------------------------------------------------- 1 | export default async promise => { 2 | try { 3 | await promise; 4 | assert.fail('Expected invalid opcode not received'); 5 | } catch (error) { 6 | const invalidOpcodeReceived = error.message.search('invalid opcode') >= 0; 7 | assert(invalidOpcodeReceived, `Expected "invalid opcode", got ${error} instead`); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /contracts/ERC20Basic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | 4 | contract ERC20Basic { 5 | // events 6 | event Transfer(address indexed from, address indexed to, uint256 value); 7 | 8 | // public functions 9 | function totalSupply() public view returns (uint256); 10 | function balanceOf(address addr) public view returns (uint256); 11 | function transfer(address to, uint256 value) public returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/TokenBlackHole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | 4 | contract TokenBlackHole { 5 | function info() public pure returns (string) { 6 | // solium-disable-next-line max-len 7 | return "The purpose of this contract is to provide the function of burnning token indirectly for those ERC20 contracts that have been published and lack of burn function. Transfer to the address of this contract will not be able to be withdrawn."; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/TokenBlackHole.test.js: -------------------------------------------------------------------------------- 1 | const TokenBlackHole = artifacts.require('TokenBlackHole'); 2 | 3 | contract('TokenBlackHole', function ([_, owner, recipient, anotherAccount]) { 4 | beforeEach(async function () { 5 | this.token = await TokenBlackHole.new(); 6 | }); 7 | 8 | describe('BlackHole', function () { 9 | it('returns the info', function () { 10 | const info = this.token.info(); 11 | assert.notEqual(info.length, 0); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./ERC20Basic.sol"; 4 | 5 | 6 | contract ERC20 is ERC20Basic { 7 | // events 8 | event Approval(address indexed owner, address indexed agent, uint256 value); 9 | 10 | // public functions 11 | function allowance(address owner, address agent) public view returns (uint256); 12 | function transferFrom(address from, address to, uint256 value) public returns (bool); 13 | function approve(address agent, uint256 value) public returns (bool); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:all", 3 | "plugins": ["security"], 4 | "rules": { 5 | "quotes": ["error", "double"], 6 | "no-empty-blocks": "off", 7 | "error-reason": ["warning", { "revert": true, "require": false }], 8 | "indentation": ["error", 2], 9 | "arg-overflow": ["warning", 3], 10 | "no-constant": ["error"], 11 | "security/enforce-explicit-visibility": ["error"], 12 | "security/no-block-members": ["warning"], 13 | "security/no-inline-assembly": ["warning"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/demos/DemoToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../StandardToken.sol"; 4 | 5 | 6 | contract DemoToken is StandardToken { 7 | // public variables 8 | string public name = "Demo Token"; 9 | string public symbol = "DEMO"; 10 | uint8 public decimals = 18; 11 | 12 | // public functions 13 | constructor() public { 14 | _totalSupply = 10000 * (10 ** uint256(decimals)); 15 | 16 | _balances[msg.sender] = _totalSupply; 17 | emit Transfer(0x0, msg.sender, _totalSupply); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/mocks/CandyTokenMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../CandyToken.sol"; 4 | 5 | 6 | contract CandyTokenMock is CandyToken { 7 | // public variables 8 | string public name = "Demo Token"; 9 | string public symbol = "DEMO"; 10 | uint8 public decimals = 18; 11 | 12 | // public functions 13 | constructor() public { 14 | _totalSupply = 10000 * (10 ** uint256(decimals)); 15 | 16 | _balances[msg.sender] = _totalSupply; 17 | emit Transfer(0x0, msg.sender, _totalSupply); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/libs/Math.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | 4 | library Math { 5 | 6 | function max(uint256 a, uint256 b) internal pure returns (uint256) { 7 | return a >= b ? a : b; 8 | } 9 | 10 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 11 | return a < b ? a : b; 12 | } 13 | 14 | function average(uint256 a, uint256 b) internal pure returns (uint256) { 15 | // (a + b) / 2 can overflow, so we distribute 16 | return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); 17 | } 18 | } -------------------------------------------------------------------------------- /test/helpers/expectEvent.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should(); 2 | 3 | const inLogs = async (logs, eventName, eventArgs = {}) => { 4 | const event = logs.find(e => e.event === eventName); 5 | should.exist(event); 6 | for (const [k, v] of Object.entries(eventArgs)) { 7 | should.exist(event.args[k]); 8 | event.args[k].should.eq(v); 9 | } 10 | return event; 11 | }; 12 | 13 | const inTransaction = async (tx, eventName, eventArgs = {}) => { 14 | const { logs } = await tx; 15 | return inLogs(logs, eventName, eventArgs); 16 | }; 17 | 18 | module.exports = { 19 | inLogs, 20 | inTransaction, 21 | }; 22 | -------------------------------------------------------------------------------- /contracts/mocks/SafeMathMock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../libs/SafeMath.sol"; 4 | 5 | 6 | contract SafeMathMock { 7 | 8 | function mul(uint256 a, uint256 b) public pure returns (uint256) { 9 | return SafeMath.mul(a, b); 10 | } 11 | 12 | function div(uint256 a, uint256 b) public pure returns (uint256) { 13 | return SafeMath.div(a, b); 14 | } 15 | 16 | function sub(uint256 a, uint256 b) public pure returns (uint256) { 17 | return SafeMath.sub(a, b); 18 | } 19 | 20 | function add(uint256 a, uint256 b) public pure returns (uint256) { 21 | return SafeMath.add(a, b); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('babel-polyfill'); 3 | 4 | module.exports = { 5 | networks: { 6 | development: { 7 | host: 'localhost', 8 | port: 8545, 9 | network_id: '*', // eslint-disable-line camelcase 10 | }, 11 | coverage: { 12 | host: 'localhost', 13 | network_id: '*', // eslint-disable-line camelcase 14 | port: 8555, 15 | gas: 0xfffffffffff, 16 | gasPrice: 0x01, 17 | }, 18 | ganache: { 19 | host: 'localhost', 20 | port: 8545, 21 | network_id: '*', // eslint-disable-line camelcase 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /contracts/BurnOwnerToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./StandardToken.sol"; 4 | import "./Ownable.sol"; 5 | 6 | 7 | contract BurnOwnerToken is StandardToken, Ownable { 8 | 9 | // events 10 | event Burn(address indexed burner, uint256 value); 11 | 12 | function burnOwner(uint256 value) public onlyOwner returns(bool) { 13 | require(value <= _balances[msg.sender]); 14 | 15 | _balances[msg.sender] = _balances[msg.sender].sub(value); 16 | _totalSupply = _totalSupply.sub(value); 17 | 18 | emit Burn(msg.sender, value); 19 | emit Transfer(msg.sender, address(0), value); 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./ERC20Basic.sol"; 4 | import "./ERC20.sol"; 5 | 6 | 7 | library SafeERC20 { 8 | 9 | function safeTransfer(ERC20Basic token, address to, uint256 value) internal { 10 | require(token.transfer(to, value)); 11 | } 12 | 13 | function safeTransferFrom( 14 | ERC20 token, 15 | address from, 16 | address to, 17 | uint256 value 18 | ) 19 | internal 20 | { 21 | require(token.transferFrom(from, to, value)); 22 | } 23 | 24 | function safeApprove(ERC20 token, address spender, uint256 value) internal { 25 | require(token.approve(spender, value)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | group: beta 4 | language: node_js 5 | node_js: 6 | - "8" 7 | cache: 8 | directories: 9 | - node_modules 10 | env: 11 | - 12 | - SOLIDITY_COVERAGE=true 13 | - SOLC_NIGHTLY=true 14 | matrix: 15 | fast_finish: true 16 | allow_failures: 17 | - env: SOLIDITY_COVERAGE=true 18 | - env: SOLC_NIGHTLY=true 19 | before_script: 20 | - truffle version 21 | script: 22 | - npm run lint 23 | - npm run lint:sol 24 | - npm run test 25 | notifications: 26 | email: 27 | recipients: 28 | - rushairer@163.com 29 | on_success: never # default: change 30 | on_failure: always # default: always 31 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | 4 | contract Ownable { 5 | 6 | // public variables 7 | address public owner; 8 | 9 | // internal variables 10 | 11 | // events 12 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 13 | 14 | // public functions 15 | constructor() public { 16 | owner = msg.sender; 17 | } 18 | 19 | modifier onlyOwner() { 20 | require(msg.sender == owner); 21 | _; 22 | } 23 | 24 | function transferOwnership(address newOwner) public onlyOwner { 25 | require(newOwner != address(0)); 26 | emit OwnershipTransferred(owner, newOwner); 27 | owner = newOwner; 28 | } 29 | 30 | // internal functions 31 | } 32 | -------------------------------------------------------------------------------- /contracts/MintableToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./StandardToken.sol"; 4 | import "./Ownable.sol"; 5 | 6 | 7 | contract MintableToken is StandardToken, Ownable { 8 | // public variables 9 | bool public mintingFinished = false; 10 | 11 | // internal variables 12 | 13 | // events 14 | event Mint(address indexed to, uint256 value); 15 | event MintFinished(); 16 | 17 | // public functions 18 | 19 | modifier canMint() { 20 | require(!mintingFinished); 21 | _; 22 | } 23 | 24 | function mint(address addr, uint256 value) public onlyOwner canMint returns (bool) { 25 | _totalSupply = _totalSupply.add(value); 26 | _balances[addr] = _balances[addr].add(value); 27 | 28 | emit Mint(addr, value); 29 | emit Transfer(address(0), addr, value); 30 | 31 | return true; 32 | } 33 | 34 | function finishMinting() public onlyOwner canMint returns (bool) { 35 | mintingFinished = true; 36 | emit MintFinished(); 37 | return true; 38 | } 39 | 40 | // internal functions 41 | } 42 | -------------------------------------------------------------------------------- /contracts/libs/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | 4 | library SafeMath { 5 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 6 | if (a == 0) { 7 | return 0; 8 | } 9 | uint256 c = a * b; 10 | assert(c / a == b); 11 | return c; 12 | } 13 | 14 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 15 | // assert(b > 0); // Solidity automatically throws when dividing by 0 16 | uint256 c = a / b; 17 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 18 | return c; 19 | } 20 | 21 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 22 | assert(b <= a); 23 | return a - b; 24 | } 25 | 26 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 27 | uint256 c = a + b; 28 | assert(c >= a); 29 | return c; 30 | } 31 | 32 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 33 | require(b != 0, errorMessage); 34 | return a % b; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /contracts/BasicToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./ERC20Basic.sol"; 4 | import "./libs/SafeMath.sol"; 5 | 6 | 7 | contract BasicToken is ERC20Basic { 8 | using SafeMath for uint256; 9 | 10 | // public variables 11 | string public name; 12 | string public symbol; 13 | uint8 public decimals = 18; 14 | 15 | // internal variables 16 | uint256 _totalSupply; 17 | mapping(address => uint256) _balances; 18 | 19 | // events 20 | 21 | // public functions 22 | function totalSupply() public view returns (uint256) { 23 | return _totalSupply; 24 | } 25 | 26 | function balanceOf(address addr) public view returns (uint256 balance) { 27 | return _balances[addr]; 28 | } 29 | 30 | function transfer(address to, uint256 value) public returns (bool) { 31 | require(to != address(0)); 32 | require(value <= _balances[msg.sender]); 33 | 34 | _balances[msg.sender] = _balances[msg.sender].sub(value); 35 | _balances[to] = _balances[to].add(value); 36 | emit Transfer(msg.sender, to, value); 37 | return true; 38 | } 39 | 40 | // internal functions 41 | 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Armors Group 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 | -------------------------------------------------------------------------------- /contracts/Freezeable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./Ownable.sol"; 4 | 5 | 6 | contract Freezeable is Ownable { 7 | // public variables 8 | 9 | // internal variables 10 | mapping(address => bool) _freezeList; 11 | 12 | // events 13 | event Freezed(address indexed freezedAddr); 14 | event UnFreezed(address indexed unfreezedAddr); 15 | 16 | // public functions 17 | function freeze(address addr) public onlyOwner returns (bool) { 18 | require(true != _freezeList[addr]); 19 | 20 | _freezeList[addr] = true; 21 | 22 | emit Freezed(addr); 23 | return true; 24 | } 25 | 26 | function unfreeze(address addr) public onlyOwner returns (bool) { 27 | require(true == _freezeList[addr]); 28 | 29 | _freezeList[addr] = false; 30 | 31 | emit UnFreezed(addr); 32 | return true; 33 | } 34 | 35 | modifier whenNotFreezed() { 36 | require(true != _freezeList[msg.sender]); 37 | _; 38 | } 39 | 40 | function isFreezed(address addr) public view returns (bool) { 41 | if (true == _freezeList[addr]) { 42 | return true; 43 | } else { 44 | return false; 45 | } 46 | } 47 | // internal functions 48 | } 49 | -------------------------------------------------------------------------------- /test/Ownable.test.js: -------------------------------------------------------------------------------- 1 | 2 | import assertRevert from './helpers/assertRevert'; 3 | 4 | var Ownable = artifacts.require('Ownable'); 5 | 6 | contract('Ownable', function (accounts) { 7 | let ownable; 8 | 9 | beforeEach(async function () { 10 | ownable = await Ownable.new(); 11 | }); 12 | 13 | it('should have an owner', async function () { 14 | let owner = await ownable.owner(); 15 | assert.isTrue(owner !== 0); 16 | }); 17 | 18 | it('changes owner after transfer', async function () { 19 | let other = accounts[1]; 20 | await ownable.transferOwnership(other); 21 | let owner = await ownable.owner(); 22 | 23 | assert.isTrue(owner === other); 24 | }); 25 | 26 | it('should prevent non-owners from transfering', async function () { 27 | const other = accounts[2]; 28 | const owner = await ownable.owner.call(); 29 | assert.isTrue(owner !== other); 30 | await assertRevert(ownable.transferOwnership(other, { from: other })); 31 | }); 32 | 33 | it('should guard ownership against stuck state', async function () { 34 | let originalOwner = await ownable.owner(); 35 | await assertRevert(ownable.transferOwnership(null, { from: originalOwner })); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/SafeERC20.test.js: -------------------------------------------------------------------------------- 1 | import EVMRevert from './helpers/EVMRevert'; 2 | 3 | require('chai') 4 | .use(require('chai-as-promised')) 5 | .should(); 6 | 7 | const SafeERC20Mock = artifacts.require('SafeERC20Mock'); 8 | 9 | contract('SafeERC20', function () { 10 | beforeEach(async function () { 11 | this.helper = await SafeERC20Mock.new(); 12 | }); 13 | 14 | it('should throw on failed transfer', async function () { 15 | await this.helper.doFailingTransfer().should.be.rejectedWith(EVMRevert); 16 | }); 17 | 18 | it('should throw on failed transferFrom', async function () { 19 | await this.helper.doFailingTransferFrom().should.be.rejectedWith(EVMRevert); 20 | }); 21 | 22 | it('should throw on failed approve', async function () { 23 | await this.helper.doFailingApprove().should.be.rejectedWith(EVMRevert); 24 | }); 25 | 26 | it('should not throw on succeeding transfer', async function () { 27 | await this.helper.doSucceedingTransfer().should.be.fulfilled; 28 | }); 29 | 30 | it('should not throw on succeeding transferFrom', async function () { 31 | await this.helper.doSucceedingTransferFrom().should.be.fulfilled; 32 | }); 33 | 34 | it('should not throw on succeeding approve', async function () { 35 | await this.helper.doSucceedingApprove().should.be.fulfilled; 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /contracts/FreezeableToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./StandardToken.sol"; 4 | import "./Freezeable.sol"; 5 | 6 | 7 | contract FreezeableToken is StandardToken, Freezeable { 8 | // public variables 9 | 10 | // internal variables 11 | 12 | // events 13 | 14 | // public functions 15 | function transfer(address to, uint256 value) public whenNotFreezed returns (bool) { 16 | require(true != _freezeList[to]); 17 | return super.transfer(to, value); 18 | } 19 | 20 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 21 | require(true != _freezeList[from]); 22 | require(true != _freezeList[to]); 23 | return super.transferFrom(from, to, value); 24 | } 25 | 26 | function approve(address agent, uint256 value) public whenNotFreezed returns (bool) { 27 | require(true != _freezeList[agent]); 28 | return super.approve(agent, value); 29 | } 30 | 31 | function increaseApproval(address agent, uint value) public whenNotFreezed returns (bool success) { 32 | require(true != _freezeList[agent]); 33 | return super.increaseApproval(agent, value); 34 | } 35 | 36 | function decreaseApproval(address agent, uint value) public whenNotFreezed returns (bool success) { 37 | require(true != _freezeList[agent]); 38 | return super.decreaseApproval(agent, value); 39 | } 40 | 41 | // internal functions 42 | } 43 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends" : [ 3 | "standard", 4 | "plugin:promise/recommended" 5 | ], 6 | "plugins": [ 7 | "promise" 8 | ], 9 | "env": { 10 | "browser" : true, 11 | "node" : true, 12 | "mocha" : true, 13 | "jest" : true 14 | }, 15 | "globals" : { 16 | "artifacts": false, 17 | "contract": false, 18 | "assert": false, 19 | "web3": false 20 | }, 21 | "rules": { 22 | 23 | // Strict mode 24 | "strict": [2, "global"], 25 | 26 | // Code style 27 | "indent": [2, 2], 28 | "quotes": [2, "single"], 29 | "semi": ["error", "always"], 30 | "space-before-function-paren": ["error", "always"], 31 | "no-use-before-define": 0, 32 | "eqeqeq": [2, "smart"], 33 | "dot-notation": [2, {"allowKeywords": true, "allowPattern": ""}], 34 | "no-redeclare": [2, {"builtinGlobals": true}], 35 | "no-trailing-spaces": [2, { "skipBlankLines": true }], 36 | "eol-last": 1, 37 | "comma-spacing": [2, {"before": false, "after": true}], 38 | "camelcase": [2, {"properties": "always"}], 39 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 40 | "comma-dangle": [1, "always-multiline"], 41 | "no-dupe-args": 2, 42 | "no-dupe-keys": 2, 43 | "no-debugger": 0, 44 | "no-undef": 2, 45 | "object-curly-spacing": [2, "always"], 46 | "max-len": [2, 120, 2], 47 | "generator-star-spacing": ["error", "before"], 48 | "promise/avoid-new": 0, 49 | "promise/always-return": 0 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/extends/TokenBatchTransfer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../libs/SafeMath.sol"; 4 | import "../ERC20.sol"; 5 | import "../SafeERC20.sol"; 6 | import "../Ownable.sol"; 7 | 8 | 9 | contract TokenBatchTransfer is Ownable { 10 | using SafeERC20 for ERC20Basic; 11 | using SafeMath for uint256; 12 | 13 | // public variables 14 | ERC20Basic public token; 15 | // events 16 | // public functions 17 | constructor (ERC20Basic tokenAddr) public { 18 | token = ERC20Basic(tokenAddr); 19 | } 20 | 21 | function changeToken(ERC20Basic tokenAddr) public onlyOwner { 22 | token = ERC20Basic(tokenAddr); 23 | } 24 | 25 | function balanceOfToken() public view returns (uint256 amount) { 26 | return token.balanceOf(address(this)); 27 | } 28 | 29 | function safeTransfer(address funder, uint256 amount) public onlyOwner { 30 | token.safeTransfer(funder, amount); 31 | } 32 | 33 | function batchTransfer(address[] funders, uint256[] amounts) public onlyOwner { 34 | require(funders.length > 0 && funders.length == amounts.length); 35 | 36 | uint256 total = token.balanceOf(this); 37 | require(total > 0); 38 | 39 | uint256 fundersTotal = 0; 40 | for (uint i = 0; i < amounts.length; i++) { 41 | fundersTotal = fundersTotal.add(amounts[i]); 42 | } 43 | require(total >= fundersTotal); 44 | 45 | for (uint j = 0; j < funders.length; j++) { 46 | token.safeTransfer(funders[j], amounts[j]); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "armors-solidity", 3 | "version": "0.1.7", 4 | "description": "Armors-solidity is a framework to build secure smart contracts on Ethereum.", 5 | "scripts": { 6 | "test": "scripts/test.sh", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix", 9 | "lint:sol": "solium -d .", 10 | "lint:sol:fix": "solium -d . --fix", 11 | "coverage": "scripts/coverage.sh" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/armors/armors-solidity.git" 16 | }, 17 | "keywords": [ 18 | "solidity", 19 | "ethereum", 20 | "smart", 21 | "contracts", 22 | "armors" 23 | ], 24 | "author": "Aben ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/armors/armors-solidity/issues" 28 | }, 29 | "homepage": "https://github.com/armors/armors-solidity#readme", 30 | "devDependencies": { 31 | "babel-polyfill": "^6.26.0", 32 | "babel-preset-es2015": "^6.24.1", 33 | "babel-preset-stage-2": "^6.24.1", 34 | "babel-preset-stage-3": "^6.24.1", 35 | "babel-register": "^6.26.0", 36 | "chai": "^4.1.2", 37 | "chai-as-promised": "^7.1.1", 38 | "chai-bignumber": "^2.0.2", 39 | "coveralls": "^3.0.1", 40 | "eslint": "^4.19.1", 41 | "eslint-config-import": "^0.13.0", 42 | "eslint-config-standard": "^11.0.0", 43 | "eslint-plugin-import": "^2.11.0", 44 | "eslint-plugin-node": "^6.0.1", 45 | "eslint-plugin-promise": "^3.7.0", 46 | "eslint-plugin-standard": "^3.1.0", 47 | "ganache-cli": "^6.1.0", 48 | "solidity-coverage": "^0.5.0", 49 | "solium": "^1.1.7", 50 | "truffle": "^4.1.8" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /contracts/StandardToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | import "./BasicToken.sol"; 3 | import "./ERC20.sol"; 4 | 5 | 6 | contract StandardToken is ERC20, BasicToken { 7 | 8 | mapping (address => mapping (address => uint256)) _allowances; 9 | 10 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 11 | require(to != address(0)); 12 | require(value <= _balances[from]); 13 | require(value <= _allowances[from][msg.sender]); 14 | 15 | _balances[from] = _balances[from].sub(value); 16 | _balances[to] = _balances[to].add(value); 17 | _allowances[from][msg.sender] = _allowances[from][msg.sender].sub(value); 18 | emit Transfer(from, to, value); 19 | return true; 20 | } 21 | 22 | function approve(address agent, uint256 value) public returns (bool) { 23 | require(agent != address(0)); 24 | 25 | _allowances[msg.sender][agent] = value; 26 | emit Approval(msg.sender, agent, value); 27 | return true; 28 | } 29 | 30 | function allowance(address owner, address agent) public view returns (uint256) { 31 | return _allowances[owner][agent]; 32 | } 33 | 34 | function increaseApproval(address agent, uint value) public returns (bool) { 35 | require(agent != address(0)); 36 | 37 | _allowances[msg.sender][agent] = _allowances[msg.sender][agent].add(value); 38 | emit Approval(msg.sender, agent, _allowances[msg.sender][agent]); 39 | return true; 40 | } 41 | 42 | function decreaseApproval(address agent, uint value) public returns (bool) { 43 | require(agent != address(0)); 44 | 45 | uint allowanceValue = _allowances[msg.sender][agent]; 46 | if (value > allowanceValue) { 47 | _allowances[msg.sender][agent] = 0; 48 | } else { 49 | _allowances[msg.sender][agent] = allowanceValue.sub(value); 50 | } 51 | emit Approval(msg.sender, agent, _allowances[msg.sender][agent]); 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/BurnOwner.test.js: -------------------------------------------------------------------------------- 1 | 2 | import assertRevert from './helpers/assertRevert'; 3 | import { inLogs } from './helpers/expectEvent'; 4 | 5 | const BigNumber = web3.BigNumber; 6 | 7 | require('chai') 8 | .use(require('chai-as-promised')) 9 | .use(require('chai-bignumber')(BigNumber)) 10 | .should(); 11 | 12 | const BurnOwnerToken = artifacts.require('BurnOwnerTokenMock'); 13 | 14 | contract('BurnOwnerToken', function ([owner, anotherAccount]) { 15 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 16 | 17 | beforeEach(async function () { 18 | this.token = await BurnOwnerToken.new(owner, 1000); 19 | }); 20 | 21 | describe('as a basic burnable token', function () { 22 | describe('when the given amount is not greater than balance of the sender', function () { 23 | const amount = 100; 24 | 25 | it('burns the requested amount', async function () { 26 | const totalSupply = await this.token.totalSupply(); 27 | 28 | await this.token.burnOwner(amount); 29 | const balance = await this.token.balanceOf(owner); 30 | 31 | assert.equal(balance, (totalSupply - amount)); 32 | }); 33 | 34 | it('emits a burn event', async function () { 35 | ({ logs: this.logs } = await this.token.burnOwner(amount)); 36 | 37 | const event = await inLogs(this.logs, 'Burn'); 38 | event.args.burner.should.eq(owner); 39 | event.args.value.should.be.bignumber.equal(amount); 40 | }); 41 | 42 | it('emits a transfer event', async function () { 43 | ({ logs: this.logs } = await this.token.burnOwner(amount)); 44 | 45 | const event = await inLogs(this.logs, 'Transfer'); 46 | event.args.from.should.eq(owner); 47 | event.args.to.should.eq(ZERO_ADDRESS); 48 | event.args.value.should.be.bignumber.equal(amount); 49 | }); 50 | }); 51 | 52 | describe('when the given amount is greater than the balance of the sender', function () { 53 | it('reverts', async function () { 54 | const greaterSender = 1001; 55 | await assertRevert(this.token.burnOwner(greaterSender)); 56 | }); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /contracts/mocks/SafeERC20Mock.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "../ERC20.sol"; 4 | import "../SafeERC20.sol"; 5 | 6 | 7 | contract ERC20FailingMock is ERC20 { 8 | function totalSupply() public view returns (uint256) { 9 | return 0; 10 | } 11 | 12 | function transfer(address, uint256) public returns (bool) { 13 | return false; 14 | } 15 | 16 | function transferFrom(address, address, uint256) public returns (bool) { 17 | return false; 18 | } 19 | 20 | function approve(address, uint256) public returns (bool) { 21 | return false; 22 | } 23 | 24 | function balanceOf(address) public view returns (uint256) { 25 | return 0; 26 | } 27 | 28 | function allowance(address, address) public view returns (uint256) { 29 | return 0; 30 | } 31 | } 32 | 33 | 34 | contract ERC20SucceedingMock is ERC20 { 35 | function totalSupply() public view returns (uint256) { 36 | return 0; 37 | } 38 | 39 | function transfer(address, uint256) public returns (bool) { 40 | return true; 41 | } 42 | 43 | function transferFrom(address, address, uint256) public returns (bool) { 44 | return true; 45 | } 46 | 47 | function approve(address, uint256) public returns (bool) { 48 | return true; 49 | } 50 | 51 | function balanceOf(address) public view returns (uint256) { 52 | return 0; 53 | } 54 | 55 | function allowance(address, address) public view returns (uint256) { 56 | return 0; 57 | } 58 | } 59 | 60 | 61 | contract SafeERC20Mock { 62 | using SafeERC20 for ERC20; 63 | 64 | ERC20 failing; 65 | ERC20 succeeding; 66 | 67 | constructor() public { 68 | failing = new ERC20FailingMock(); 69 | succeeding = new ERC20SucceedingMock(); 70 | } 71 | 72 | function doFailingTransfer() public { 73 | failing.safeTransfer(0, 0); 74 | } 75 | 76 | function doFailingTransferFrom() public { 77 | failing.safeTransferFrom(0, 0, 0); 78 | } 79 | 80 | function doFailingApprove() public { 81 | failing.safeApprove(0, 0); 82 | } 83 | 84 | function doSucceedingTransfer() public { 85 | succeeding.safeTransfer(0, 0); 86 | } 87 | 88 | function doSucceedingTransferFrom() public { 89 | succeeding.safeTransferFrom(0, 0, 0); 90 | } 91 | 92 | function doSucceedingApprove() public { 93 | succeeding.safeApprove(0, 0); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Armors-solidity 2 | 3 | [![NPM Package](https://img.shields.io/npm/v/armors-solidity.svg)](https://www.npmjs.com/package/armors-solidity) 4 | [![Travis](https://travis-ci.com/armors/armors-solidity.svg?branch=master)](https://travis-ci.com/armors/armors-solidity) 5 | [![Coverage Status](https://coveralls.io/repos/github/armors/armors-solidity/badge.svg)](https://coveralls.io/github/armors/armors-solidity) 6 | 7 | Armors-solidity is a framework to build secure smart contracts on Ethereum.The code here will include a secure library, common smart contract code implementations, and some common industry solutions. 8 | 9 | ## Getting Started 10 | 11 | Armors-solidity integrates with [Truffle](https://github.com/ConsenSys/truffle), an Ethereum development environment. Please install Truffle and initialize your dapp with `truffle init`. 12 | 13 | ``` shell 14 | npm install -g truffle 15 | mkdir dapp && cd dapp 16 | truffle init 17 | ``` 18 | 19 | To install the Armors library, run the following in your Solidity project root directory: 20 | 21 | ``` shell 22 | npm init -y 23 | npm install armors-solidity 24 | ``` 25 | 26 | ## License 27 | Code released under the [MIT License](https://github.com/armors/armors-solidity/blob/master/LICENSE). 28 | 29 | Copyright (c) 2018 Armors Group 30 | Permission is hereby granted, free of charge, to any person obtaining a copy 31 | of this software and associated documentation files (the "Software"), to deal 32 | in the Software without restriction, including without limitation the rights 33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | copies of the Software, and to permit persons to whom the Software is 35 | furnished to do so, subject to the following conditions: 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | 47 | This repository code references open source code from the ethereum.org, 0xproject.com, openzeppelin.org project and follows the corresponding open source licence. 48 | 49 | **Please note that Armors is your technical partner in all your publicity materials and official website.** 50 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -o errexit 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the ganache instance that we started (if we started one and if it's still running). 11 | if [ -n "$ganache_pid" ] && ps -p $ganache_pid > /dev/null; then 12 | kill -9 $ganache_pid 13 | fi 14 | } 15 | 16 | if [ "$SOLIDITY_COVERAGE" = true ]; then 17 | ganache_port=8555 18 | else 19 | ganache_port=8545 20 | fi 21 | 22 | ganache_running() { 23 | nc -z localhost "$ganache_port" 24 | } 25 | 26 | start_ganache() { 27 | # We define 10 accounts with balance 1M ether, needed for high-value tests. 28 | local accounts=( 29 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a00,1000000000000000000000000" 30 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a01,1000000000000000000000000" 31 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a02,1000000000000000000000000" 32 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a03,1000000000000000000000000" 33 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a04,1000000000000000000000000" 34 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a05,1000000000000000000000000" 35 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a06,1000000000000000000000000" 36 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a07,1000000000000000000000000" 37 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a08,1000000000000000000000000" 38 | --account="0x75a5ba3fd7c0619b4db68c6fa518f9a41ac8196906ec274b1080d6ee3a2e1a09,1000000000000000000000000" 39 | ) 40 | 41 | if [ "$SOLIDITY_COVERAGE" = true ]; then 42 | node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --port "$ganache_port" "${accounts[@]}" > /dev/null & 43 | else 44 | node_modules/.bin/ganache-cli --gasLimit 0xfffffffffff "${accounts[@]}" > /dev/null & 45 | fi 46 | 47 | ganache_pid=$! 48 | } 49 | 50 | if ganache_running; then 51 | echo "Using existing ganache instance" 52 | else 53 | echo "Starting our own ganache instance" 54 | start_ganache 55 | fi 56 | 57 | if [ "$SOLC_NIGHTLY" = true ]; then 58 | echo "Downloading solc nightly" 59 | wget -q https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/soljson-nightly.js -O /tmp/soljson.js && find . -name soljson.js -exec cp /tmp/soljson.js {} \; 60 | fi 61 | 62 | if [ "$SOLIDITY_COVERAGE" = true ]; then 63 | node_modules/.bin/solidity-coverage 64 | 65 | if [ "$CONTINUOUS_INTEGRATION" = true ]; then 66 | cat coverage/lcov.info | node_modules/.bin/coveralls 67 | fi 68 | else 69 | node_modules/.bin/truffle test "$@" 70 | fi 71 | -------------------------------------------------------------------------------- /test/BasicToken.test.js: -------------------------------------------------------------------------------- 1 | import assertRevert from './helpers/assertRevert'; 2 | const BasicTokenMock = artifacts.require('BasicTokenMock'); 3 | 4 | contract('BasicToken', function ([_, owner, recipient, anotherAccount]) { 5 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 6 | 7 | beforeEach(async function () { 8 | this.token = await BasicTokenMock.new(owner, 100); 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 | }); 48 | 49 | describe('when the sender has enough balance', function () { 50 | const amount = 100; 51 | 52 | it('transfers the requested amount', async function () { 53 | await this.token.transfer(to, amount, { from: owner }); 54 | 55 | const senderBalance = await this.token.balanceOf(owner); 56 | assert.equal(senderBalance, 0); 57 | 58 | const recipientBalance = await this.token.balanceOf(to); 59 | assert.equal(recipientBalance, amount); 60 | }); 61 | 62 | it('emits a transfer event', async function () { 63 | const { logs } = await this.token.transfer(to, amount, { from: owner }); 64 | 65 | assert.equal(logs.length, 1); 66 | assert.equal(logs[0].event, 'Transfer'); 67 | assert.equal(logs[0].args.from, owner); 68 | assert.equal(logs[0].args.to, to); 69 | assert(logs[0].args.value.eq(amount)); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('when the recipient is the zero address', function () { 75 | const to = ZERO_ADDRESS; 76 | 77 | it('reverts', async function () { 78 | await assertRevert(this.token.transfer(to, 100, { from: owner })); 79 | }); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/SafeMath.test.js: -------------------------------------------------------------------------------- 1 | import assertJump from './helpers/assertJump'; 2 | const BigNumber = web3.BigNumber; 3 | const SafeMathMock = artifacts.require('SafeMathMock'); 4 | 5 | require('chai') 6 | .use(require('chai-bignumber')(BigNumber)) 7 | .should(); 8 | 9 | contract('SafeMath', () => { 10 | const MAX_UINT = new BigNumber('115792089237316195423570985008687907853269984665640564039457584007913129639935'); 11 | 12 | before(async function () { 13 | this.safeMath = await SafeMathMock.new(); 14 | }); 15 | 16 | describe('add', function () { 17 | it('adds correctly', async function () { 18 | const a = new BigNumber(5678); 19 | const b = new BigNumber(1234); 20 | 21 | const result = await this.safeMath.add(a, b); 22 | result.should.be.bignumber.equal(a.plus(b)); 23 | }); 24 | 25 | it('throws an error on addition overflow', async function () { 26 | const a = MAX_UINT; 27 | const b = new BigNumber(1); 28 | 29 | await assertJump(this.safeMath.add(a, b)); 30 | }); 31 | }); 32 | 33 | describe('sub', function () { 34 | it('subtracts correctly', async function () { 35 | const a = new BigNumber(5678); 36 | const b = new BigNumber(1234); 37 | 38 | const result = await this.safeMath.sub(a, b); 39 | result.should.be.bignumber.equal(a.minus(b)); 40 | }); 41 | 42 | it('throws an error if subtraction result would be negative', async function () { 43 | const a = new BigNumber(1234); 44 | const b = new BigNumber(5678); 45 | 46 | await assertJump(this.safeMath.sub(a, b)); 47 | }); 48 | }); 49 | 50 | describe('mul', function () { 51 | it('multiplies correctly', async function () { 52 | const a = new BigNumber(1234); 53 | const b = new BigNumber(5678); 54 | 55 | const result = await this.safeMath.mul(a, b); 56 | result.should.be.bignumber.equal(a.times(b)); 57 | }); 58 | 59 | it('handles a zero product correctly', async function () { 60 | const a = new BigNumber(0); 61 | const b = new BigNumber(5678); 62 | 63 | const result = await this.safeMath.mul(a, b); 64 | result.should.be.bignumber.equal(a.times(b)); 65 | }); 66 | 67 | it('throws an error on multiplication overflow', async function () { 68 | const a = MAX_UINT; 69 | const b = new BigNumber(2); 70 | 71 | await assertJump(this.safeMath.mul(a, b)); 72 | }); 73 | }); 74 | 75 | describe('div', function () { 76 | it('divides correctly', async function () { 77 | const a = new BigNumber(5678); 78 | const b = new BigNumber(5678); 79 | 80 | const result = await this.safeMath.div(a, b); 81 | result.should.be.bignumber.equal(a.div(b)); 82 | }); 83 | 84 | it('throws an error on zero division', async function () { 85 | const a = new BigNumber(5678); 86 | const b = new BigNumber(0); 87 | 88 | await assertJump(this.safeMath.div(a, b)); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /contracts/CandyToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import "./StandardToken.sol"; 4 | import "./Ownable.sol"; 5 | 6 | 7 | contract CandyToken is StandardToken, Ownable { 8 | // events 9 | event Airdrop(address indexed beneficiary, uint256 value); 10 | 11 | // public variables 12 | uint256 public candy = 0; 13 | 14 | address public airdropWallet; 15 | 16 | bool airdropFinished; 17 | 18 | // internal variables 19 | mapping (address => bool) _beneficiaries; 20 | 21 | // public functions 22 | modifier onlyAirdropFinished() { 23 | require(_allowances[airdropWallet][this] == 0); 24 | _; 25 | } 26 | 27 | function getAirdropFinished() public view returns (bool) { 28 | return _allowances[airdropWallet][this] == 0; 29 | } 30 | 31 | function initAirdrop(uint256 newCandyAmount, address newAirdropWallet) public onlyOwner onlyAirdropFinished { 32 | candy = newCandyAmount * (10 ** uint256(decimals)); 33 | airdropWallet = newAirdropWallet; 34 | } 35 | 36 | function balanceOf(address addr) public view returns (uint256 balance) { 37 | if (_allowances[airdropWallet][this] == 0 || addr == airdropWallet || addr == owner) { 38 | return _balances[addr]; 39 | } else { 40 | if (_beneficiaries[addr] == true) { 41 | return _balances[addr]; 42 | } else { 43 | return _balances[addr].add(candy); 44 | } 45 | } 46 | } 47 | 48 | function transfer(address to, uint256 value) public returns (bool) { 49 | require(isContract(to) == false); 50 | require(to != address(0)); 51 | 52 | // solium-disable-next-line operator-whitespace 53 | if ((_allowances[airdropWallet][this] != 0) && 54 | (_allowances[airdropWallet][this] <= _balances[airdropWallet]) && 55 | (to != airdropWallet) && 56 | (to != owner)) { 57 | if (msg.sender == owner && value == candy && _beneficiaries[to] != true) { 58 | emit Transfer(msg.sender, to, value); 59 | return true; 60 | } else { 61 | airdrop(msg.sender); 62 | } 63 | } 64 | 65 | require(value <= _balances[msg.sender]); 66 | 67 | _balances[msg.sender] = _balances[msg.sender].sub(value); 68 | _balances[to] = _balances[to].add(value); 69 | emit Transfer(msg.sender, to, value); 70 | return true; 71 | } 72 | 73 | function transferFrom(address from, address to, uint256 value) public returns (bool) { 74 | require(isContract(to) == false); 75 | require(to != address(0)); 76 | require(value <= _balances[from]); 77 | require(value <= _allowances[from][msg.sender]); 78 | 79 | _balances[from] = _balances[from].sub(value); 80 | _balances[to] = _balances[to].add(value); 81 | _allowances[from][msg.sender] = _allowances[from][msg.sender].sub(value); 82 | emit Transfer(from, to, value); 83 | return true; 84 | } 85 | 86 | function airdropped (address beneficiary) public view returns (bool) { 87 | return _beneficiaries[beneficiary]; 88 | } 89 | 90 | function airdrop(address beneficiary) private { 91 | if (_allowances[airdropWallet][this] != 0 && _beneficiaries[beneficiary] != true) { 92 | this.transferFrom(airdropWallet, beneficiary, candy); 93 | _beneficiaries[beneficiary] = true; 94 | emit Airdrop(beneficiary, candy); 95 | } 96 | } 97 | 98 | function isContract(address addr) internal view returns (bool) { 99 | uint size; 100 | // solium-disable-next-line security/no-inline-assembly 101 | assembly { size := extcodesize(addr) } 102 | return size > 0; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /test/MintableToken.test.js: -------------------------------------------------------------------------------- 1 | import assertRevert from './helpers/assertRevert'; 2 | const MintableToken = artifacts.require('MintableToken'); 3 | 4 | contract('Mintable', function ([owner, anotherAccount]) { 5 | beforeEach(async function () { 6 | this.token = await MintableToken.new({ from: owner }); 7 | }); 8 | 9 | describe('minting finished', function () { 10 | describe('when the token is not finished', function () { 11 | it('returns false', async function () { 12 | const mintingFinished = await this.token.mintingFinished(); 13 | assert.equal(mintingFinished, false); 14 | }); 15 | }); 16 | 17 | describe('when the token is finished', function () { 18 | beforeEach(async function () { 19 | await this.token.finishMinting({ from: owner }); 20 | }); 21 | 22 | it('returns true', async function () { 23 | const mintingFinished = await this.token.mintingFinished.call(); 24 | assert.equal(mintingFinished, true); 25 | }); 26 | }); 27 | }); 28 | 29 | describe('finish minting', function () { 30 | describe('when the sender is the token owner', function () { 31 | const from = owner; 32 | 33 | describe('when the token was not finished', function () { 34 | it('finishes token minting', async function () { 35 | await this.token.finishMinting({ from }); 36 | 37 | const mintingFinished = await this.token.mintingFinished(); 38 | assert.equal(mintingFinished, true); 39 | }); 40 | 41 | it('emits a mint finished event', async function () { 42 | const { logs } = await this.token.finishMinting({ from }); 43 | 44 | assert.equal(logs.length, 1); 45 | assert.equal(logs[0].event, 'MintFinished'); 46 | }); 47 | }); 48 | 49 | describe('when the token was already finished', function () { 50 | beforeEach(async function () { 51 | await this.token.finishMinting({ from }); 52 | }); 53 | 54 | it('reverts', async function () { 55 | await assertRevert(this.token.finishMinting({ from })); 56 | }); 57 | }); 58 | }); 59 | 60 | describe('when the sender is not the token owner', function () { 61 | const from = anotherAccount; 62 | 63 | describe('when the token was not finished', function () { 64 | it('reverts', async function () { 65 | await assertRevert(this.token.finishMinting({ from })); 66 | }); 67 | }); 68 | 69 | describe('when the token was already finished', function () { 70 | beforeEach(async function () { 71 | await this.token.finishMinting({ from: owner }); 72 | }); 73 | 74 | it('reverts', async function () { 75 | await assertRevert(this.token.finishMinting({ from })); 76 | }); 77 | }); 78 | }); 79 | }); 80 | 81 | describe('mint', function () { 82 | const amount = 100; 83 | 84 | describe('when the sender is the token owner', function () { 85 | const from = owner; 86 | 87 | describe('when the token was not finished', function () { 88 | it('mints the requested amount', async function () { 89 | await this.token.mint(owner, amount, { from }); 90 | 91 | const balance = await this.token.balanceOf(owner); 92 | assert.equal(balance, amount); 93 | }); 94 | 95 | it('emits a mint finished event', async function () { 96 | const { logs } = await this.token.mint(owner, amount, { from }); 97 | 98 | assert.equal(logs.length, 2); 99 | assert.equal(logs[0].event, 'Mint'); 100 | assert.equal(logs[0].args.to, owner); 101 | assert.equal(logs[0].args.value, amount); 102 | assert.equal(logs[1].event, 'Transfer'); 103 | }); 104 | }); 105 | 106 | describe('when the token minting is finished', function () { 107 | beforeEach(async function () { 108 | await this.token.finishMinting({ from }); 109 | }); 110 | 111 | it('reverts', async function () { 112 | await assertRevert(this.token.mint(owner, amount, { from })); 113 | }); 114 | }); 115 | }); 116 | 117 | describe('when the sender is not the token owner', function () { 118 | const from = anotherAccount; 119 | 120 | describe('when the token was not finished', function () { 121 | it('reverts', async function () { 122 | await assertRevert(this.token.mint(owner, amount, { from })); 123 | }); 124 | }); 125 | 126 | describe('when the token was already finished', function () { 127 | beforeEach(async function () { 128 | await this.token.finishMinting({ from: owner }); 129 | }); 130 | 131 | it('reverts', async function () { 132 | await assertRevert(this.token.mint(owner, amount, { from })); 133 | }); 134 | }); 135 | }); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/TokenBatchTransfer.test.js: -------------------------------------------------------------------------------- 1 | 2 | import assertRevert from './helpers/assertRevert'; 3 | 4 | const StandardTokenMock = artifacts.require('StandardTokenMock'); 5 | 6 | const Ownable = artifacts.require('Ownable'); 7 | 8 | const TokenBatchTransfer = artifacts.require('TokenBatchTransfer'); 9 | 10 | contract('TokenBatchTransfer', function ([_, owner, recipient, anotherAccount, thirdAccount]) { 11 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 12 | 13 | beforeEach(async function () { 14 | this.token = await StandardTokenMock.new(owner, 10000); 15 | this.ownable = await Ownable.new(); 16 | this.tokenBatch = await TokenBatchTransfer.new(this.token.address); 17 | }); 18 | 19 | describe('token', function () { 20 | it('When the token is StandardTokenMock adress', async function () { 21 | const tokenAddress = await this.tokenBatch.token(); 22 | 23 | assert.equal(tokenAddress, this.token.address); 24 | }); 25 | }); 26 | 27 | describe('balanceOfToken', function () { 28 | it('When the TokenBatchTransfe contract has no token', async function () { 29 | const balance = await this.tokenBatch.balanceOfToken(); 30 | assert.equal(balance, 0); 31 | }); 32 | }); 33 | 34 | describe('changeToken', function () { 35 | it('When change token', async function () { 36 | await this.tokenBatch.changeToken(this.ownable.address); 37 | const tokenAddress = await this.tokenBatch.token(); 38 | assert.equal(tokenAddress, this.ownable.address); 39 | }); 40 | }); 41 | 42 | describe('safeTransfer', function () { 43 | it('When the TokenBatchTransfer has some tokens', async function () { 44 | await this.token.transfer(this.tokenBatch.address, 100, { from: owner }); 45 | 46 | const balance = await this.tokenBatch.balanceOfToken(); 47 | assert.equal(balance, 100); 48 | 49 | await this.tokenBatch.safeTransfer(recipient, 10); 50 | 51 | const balance1 = await this.tokenBatch.balanceOfToken(); 52 | assert.equal(balance1, 90); 53 | 54 | const balance2 = await this.token.balanceOf(recipient); 55 | assert.equal(balance2, 10); 56 | }); 57 | 58 | describe('When the TokenBatchTransfer has no tokens', function () { 59 | it('reverts', async function () { 60 | await assertRevert(this.tokenBatch.safeTransfer(recipient, 10)); 61 | }); 62 | }); 63 | describe('When the sender is not owner', function () { 64 | it('reverts', async function () { 65 | await this.token.transfer(this.tokenBatch.address, 10000, { from: owner }); 66 | await assertRevert(this.tokenBatch.safeTransfer(recipient, 10, { from: anotherAccount })); 67 | }); 68 | }); 69 | 70 | describe('when the recipient is the zero address', function () { 71 | const to = ZERO_ADDRESS; 72 | 73 | it('reverts', async function () { 74 | await this.token.transfer(this.tokenBatch.address, 100, { from: owner }); 75 | await assertRevert(this.tokenBatch.safeTransfer(to, 10)); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('batchTransfer', function () { 81 | it('When the TokenBatchTransfer has some tokens', async function () { 82 | await this.token.transfer(this.tokenBatch.address, 10000, { from: owner }); 83 | 84 | const balance = await this.tokenBatch.balanceOfToken(); 85 | assert.equal(balance, 10000); 86 | 87 | await this.tokenBatch.batchTransfer([recipient, anotherAccount, thirdAccount], [1000, 1000, 1000]); 88 | 89 | const balance1 = await this.tokenBatch.balanceOfToken(); 90 | assert.equal(balance1, 7000); 91 | const balance2 = await this.token.balanceOf(recipient); 92 | assert.equal(balance2, 1000); 93 | const balance3 = await this.token.balanceOf(anotherAccount); 94 | assert.equal(balance3, 1000); 95 | const balance4 = await this.token.balanceOf(thirdAccount); 96 | assert.equal(balance4, 1000); 97 | }); 98 | 99 | describe('When the batchTransfer transfer more then it has', function () { 100 | it('reverts', async function () { 101 | await assertRevert(this.tokenBatch.batchTransfer( 102 | [recipient, anotherAccount, thirdAccount], 103 | [5000, 5000, 1000] 104 | )); 105 | }); 106 | }); 107 | 108 | describe('When the TokenBatchTransfer has no tokens', function () { 109 | it('reverts', async function () { 110 | await assertRevert(this.tokenBatch.batchTransfer( 111 | [recipient, anotherAccount, thirdAccount], 112 | [1000, 1000, 1000] 113 | )); 114 | }); 115 | }); 116 | 117 | describe('when the recipient is the zero address', function () { 118 | const to = ZERO_ADDRESS; 119 | 120 | it('reverts', async function () { 121 | await this.token.transfer(this.tokenBatch.address, 100, { from: owner }); 122 | await assertRevert(this.tokenBatch.batchTransfer([to], [10])); 123 | }); 124 | }); 125 | 126 | describe('When the sender is not owner', function () { 127 | it('reverts', async function () { 128 | await this.token.transfer(this.tokenBatch.address, 10000, { from: owner }); 129 | await assertRevert(this.tokenBatch.batchTransfer([recipient], [10], { from: anotherAccount })); 130 | }); 131 | }); 132 | 133 | describe('When the contract has enough token.', function () { 134 | it('reverts', async function () { 135 | await this.token.transfer(this.tokenBatch.address, 10000, { from: owner }); 136 | await assertRevert(this.tokenBatch.batchTransfer([recipient], [10100])); 137 | }); 138 | }); 139 | 140 | describe('When the contract batchTransfer param no address.', function () { 141 | it('reverts', async function () { 142 | await this.token.transfer(this.tokenBatch.address, 10000, { from: owner }); 143 | await assertRevert(this.tokenBatch.batchTransfer([], [10100])); 144 | }); 145 | }); 146 | 147 | describe('When the contract batchTransfer param diff lenth.', function () { 148 | it('reverts', async function () { 149 | await this.token.transfer(this.tokenBatch.address, 10000, { from: owner }); 150 | await assertRevert(this.tokenBatch.batchTransfer([recipient], [10100, 10000])); 151 | }); 152 | }); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /test/CandyToken.test.js: -------------------------------------------------------------------------------- 1 | import assertRevert from './helpers/assertRevert'; 2 | const CandyTokenMock = artifacts.require('CandyTokenMock'); 3 | 4 | contract('CandyTokenMock', function ([owner, recipient, airdropWallet, 5 | test4, test5, test6, test7, test8, test9, test10]) { 6 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 7 | const PAY = 5 * (10 ** 18); 8 | 9 | beforeEach(async function () { 10 | this.token = await CandyTokenMock.new(); 11 | }); 12 | 13 | describe('total supply', function () { 14 | it('returns the total amount of tokens,totalSupply notEqual zero', async function () { 15 | const totalSupply = await this.token.totalSupply(); 16 | assert.notEqual(totalSupply, 0); 17 | }); 18 | }); 19 | 20 | describe('balanceOf', function () { 21 | describe('when the requested account has no tokens', function () { 22 | it('returns zero', async function () { 23 | const balance = await this.token.balanceOf(owner); 24 | assert.notEqual(balance, 0); 25 | }); 26 | }); 27 | 28 | describe('transfer to airdropWallet some token', function () { 29 | it('returns the total amount of tokens', async function () { 30 | await this.token.transfer(airdropWallet, 10); 31 | assert.notEqual(airdropWallet, 0); 32 | }); 33 | }); 34 | 35 | describe('candy test', function () { 36 | describe('candy initial', function () { 37 | it('return CandyToken', async function () { 38 | await this.token.initAirdrop(5, airdropWallet); 39 | const candy = await this.token.candy(); 40 | assert.notEqual(candy, 0); 41 | }); 42 | }); 43 | 44 | describe('candy initial not owner', function () { 45 | it('reverts', async function () { 46 | await assertRevert(this.token.initAirdrop(5, airdropWallet, { from: test10 })); 47 | }); 48 | }); 49 | 50 | describe('candy airdrop', function () { 51 | it('wallet not enough money,transfer from owner', async function () { 52 | await this.token.initAirdrop(5, airdropWallet); 53 | await this.token.approve(this.token.address, 10); 54 | await this.token.transfer(test5, 100); 55 | const test5Balance = await this.token.balanceOf(test5); 56 | assert.notEqual(test5Balance, 0); 57 | }); 58 | }); 59 | 60 | describe('transfer owner,candy,not airdropped', function () { 61 | it('returns the transfer result', async function () { 62 | await this.token.initAirdrop(5, airdropWallet); 63 | await this.token.transfer(airdropWallet, 80 * (10 ** 18)); 64 | await this.token.approve(this.token.address, 20 * (10 ** 18), { from: airdropWallet }); 65 | const candy = await this.token.candy(); 66 | await this.token.transfer(test5, candy, { from: owner }); 67 | const test5Balance = await this.token.balanceOf(test5); 68 | assert.notEqual(test5Balance, 0); 69 | }); 70 | }); 71 | }); 72 | 73 | describe('candy airdrop', function () { 74 | it('return airdrop enough money from wallet', async function () { 75 | await this.token.initAirdrop(5, airdropWallet); 76 | await this.token.transfer(airdropWallet, 80 * (10 ** 18)); 77 | await this.token.approve(this.token.address, 20 * (10 ** 18), { from: airdropWallet }); 78 | const rs = await this.token.getAirdropFinished(); 79 | assert.equal(rs, false); 80 | await this.token.transfer(test4, 5, { from: airdropWallet }); 81 | const test4Balance = await this.token.balanceOf(test4); 82 | assert.equal(test4Balance, PAY + 5); 83 | const airdropWalletBalance = await this.token.balanceOf(airdropWallet); 84 | assert.equal(airdropWalletBalance, 80 * (10 ** 18) - 5); 85 | const test7Balance = await this.token.balanceOf(test7); 86 | assert.equal(test7Balance, PAY); 87 | }); 88 | }); 89 | 90 | describe('candy airdrop', function () { 91 | it('return airdrop enough money from same anotherAccount', async function () { 92 | await this.token.initAirdrop(5, airdropWallet);// 每次转5个 93 | await this.token.transfer(airdropWallet, 80 * (10 ** 18)); 94 | await this.token.approve(this.token.address, 20 * (10 ** 18), { from: airdropWallet });// 授权转10个 95 | await this.token.transfer(test4, 5, { from: test10 }); 96 | const test10Balance = await this.token.balanceOf(test10); 97 | const test10Status = await this.token.airdropped(test10); 98 | const test4Balance = await this.token.balanceOf(test4); 99 | assert.equal(test10Balance, PAY - 5); 100 | assert.equal(test10Status, true); 101 | assert.equal(test4Balance, PAY + 5); 102 | 103 | await this.token.transfer(test4, 5, { from: test10 }); 104 | const test10Balance1 = await this.token.balanceOf(test10); 105 | const test10Status1 = await this.token.airdropped(test10); 106 | const test4Balance1 = await this.token.balanceOf(test4); 107 | assert.equal(test10Balance1, PAY - 10); 108 | assert.equal(test10Status1, true); 109 | assert.equal(test4Balance1, PAY + 10); 110 | 111 | const test4Status = await this.token.airdropped(test4); 112 | assert.equal(test4Status, false); 113 | }); 114 | }); 115 | 116 | describe('candy airdrop', function () { 117 | it('return airdrop enough money from anotherAccount', async function () { 118 | await this.token.initAirdrop(5, airdropWallet);// 每次转5个 119 | await this.token.transfer(airdropWallet, 80 * (10 ** 18)); 120 | await this.token.approve(this.token.address, 20 * (10 ** 18), { from: airdropWallet });// 授权转10个 121 | 122 | await this.token.transfer(test5, 5, { from: test6 }); 123 | const test6Balance = await this.token.balanceOf(test6); 124 | const test6Status = await this.token.airdropped(test6); 125 | const test5Balance = await this.token.balanceOf(test5); 126 | assert.equal(test6Balance, PAY - 5); 127 | assert.equal(test6Status, true); 128 | assert.equal(test5Balance, PAY + 5); 129 | 130 | await this.token.transfer(test8, 5, { from: test5 }); 131 | const test5Balance1 = await this.token.balanceOf(test5); 132 | const test5Status1 = await this.token.airdropped(test5); 133 | const test8Balance = await this.token.balanceOf(test8); 134 | assert.equal(test5Balance1, PAY); 135 | assert.equal(test5Status1, true); 136 | assert.equal(test8Balance, PAY + 5); 137 | const test8Status = await this.token.airdropped(test8); 138 | assert.equal(test8Status, false); 139 | }); 140 | }); 141 | 142 | describe('branch', function () { 143 | describe('ZERO_ADDRESS test', function () { 144 | it('reverts ZERO_ADDRESS', async function () { 145 | await assertRevert(this.token.transfer(ZERO_ADDRESS, 1)); 146 | }); 147 | }); 148 | 149 | describe('onlyAirdropFinished test', function () { 150 | it('reverts onlyAirdropFinished', async function () { 151 | await this.token.initAirdrop(5, airdropWallet); 152 | await this.token.transfer(airdropWallet, 80 * (10 ** 18)); 153 | await this.token.approve(this.token.address, 20 * (10 ** 18), { from: airdropWallet }); 154 | await assertRevert(this.token.initAirdrop(5, airdropWallet)); 155 | }); 156 | }); 157 | 158 | describe('transfer value <= balance[sender] test', function () { 159 | it('reverts value <= balance[sender', async function () { 160 | await assertRevert(this.token.transfer(5, test5)); 161 | }); 162 | }); 163 | 164 | describe('transferFrom add[0] test', function () { 165 | it('reverts transferFrom add[0]', async function () { 166 | await assertRevert(this.token.transferFrom(owner, ZERO_ADDRESS, 10)); 167 | }); 168 | }); 169 | 170 | describe('transferFrom value <= balance[from] test', function () { 171 | it('reverts value <= balance[from] ', async function () { 172 | await assertRevert(this.token.transferFrom(test5, test10, 10)); 173 | }); 174 | }); 175 | 176 | describe('transferFrom value <= _allowances[from][msg.sender] test', function () { 177 | it('reverts value <= _allowances[from][msg.sender] ', async function () { 178 | await this.token.approve(airdropWallet, 100); 179 | await assertRevert(this.token.transferFrom(owner, test10, 100)); 180 | }); 181 | }); 182 | }); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /test/FreezeableToken.test.js: -------------------------------------------------------------------------------- 1 | 2 | import assertRevert from './helpers/assertRevert'; 3 | 4 | const FreezeableToken = artifacts.require('FreezeableTokenMock'); 5 | 6 | contract('FreezeableToken', function ([_, owner, recipient, anotherAccount, approveAccount]) { 7 | beforeEach(async function () { 8 | this.token = await FreezeableToken.new(recipient, 100, { from: owner }); 9 | }); 10 | 11 | describe('freeze', function () { 12 | describe('when the sender is the token owner', function () { 13 | const from = owner; 14 | describe('when the address is unfreezed', function () { 15 | it('freeze the address', async function () { 16 | await this.token.freeze(recipient, { from }); 17 | 18 | const freezed = await this.token.isFreezed(recipient); 19 | assert.equal(freezed, true); 20 | }); 21 | 22 | it('emits a Freezed event', async function () { 23 | const { logs } = await this.token.freeze(recipient, { from }); 24 | 25 | assert.equal(logs.length, 1); 26 | assert.equal(logs[0].event, 'Freezed'); 27 | }); 28 | }); 29 | 30 | describe('when the address is freezed', function () { 31 | beforeEach(async function () { 32 | await this.token.freeze(recipient, { from }); 33 | }); 34 | 35 | it('reverts', async function () { 36 | await assertRevert(this.token.freeze(recipient, { from })); 37 | }); 38 | }); 39 | }); 40 | describe('when the sender is not the token owner', function () { 41 | const from = anotherAccount; 42 | 43 | it('reverts', async function () { 44 | await assertRevert(this.token.freeze(recipient, { from })); 45 | }); 46 | }); 47 | }); 48 | 49 | describe('unfreeze', function () { 50 | describe('when the sender is the token owner', function () { 51 | const from = owner; 52 | describe('when the address is freezed', function () { 53 | beforeEach(async function () { 54 | await this.token.freeze(recipient, { from }); 55 | }); 56 | 57 | it('unfreeze the address', async function () { 58 | await this.token.unfreeze(recipient, { from }); 59 | 60 | const freezed = await this.token.isFreezed(recipient); 61 | assert.equal(freezed, false); 62 | }); 63 | 64 | it('emits a UnFreezed event', async function () { 65 | const { logs } = await this.token.unfreeze(recipient, { from }); 66 | 67 | assert.equal(logs.length, 1); 68 | assert.equal(logs[0].event, 'UnFreezed'); 69 | }); 70 | }); 71 | 72 | describe('when the address is unfreezed', function () { 73 | it('reverts', async function () { 74 | await assertRevert(this.token.unfreeze(recipient, { from })); 75 | }); 76 | }); 77 | }); 78 | describe('when the sender is not the token owner', function () { 79 | const from = anotherAccount; 80 | 81 | it('reverts', async function () { 82 | await assertRevert(this.token.unfreeze(recipient, { from })); 83 | }); 84 | }); 85 | }); 86 | 87 | describe('freezeable token', function () { 88 | const from = owner; 89 | 90 | describe('freezed', function () { 91 | it('is not freezed by default', async function () { 92 | const freezed = await this.token.isFreezed(recipient); 93 | 94 | assert.equal(freezed, false); 95 | }); 96 | 97 | it('is freezed after being freezed', async function () { 98 | await this.token.freeze(recipient, { from }); 99 | const freezed = await this.token.isFreezed(recipient); 100 | 101 | assert.equal(freezed, true); 102 | }); 103 | 104 | it('is not freezed after being freezed and then unfreezed', async function () { 105 | await this.token.freeze(recipient, { from }); 106 | await this.token.unfreeze(recipient, { from }); 107 | const freezed = await this.token.isFreezed(recipient); 108 | 109 | assert.equal(freezed, false); 110 | }); 111 | }); 112 | 113 | describe('transfer', function () { 114 | it('allows to transfer when unfreezed', async function () { 115 | await this.token.transfer(anotherAccount, 100, { from: recipient }); 116 | 117 | const senderBalance = await this.token.balanceOf(recipient); 118 | assert.equal(senderBalance, 0); 119 | 120 | const recipientBalance = await this.token.balanceOf(anotherAccount); 121 | assert.equal(recipientBalance, 100); 122 | }); 123 | 124 | it('allows to transfer when freezed and then unfreezed', async function () { 125 | await this.token.freeze(recipient, { from: owner }); 126 | await this.token.unfreeze(recipient, { from: owner }); 127 | 128 | await this.token.transfer(anotherAccount, 100, { from: recipient }); 129 | 130 | const senderBalance = await this.token.balanceOf(recipient); 131 | assert.equal(senderBalance, 0); 132 | 133 | const recipientBalance = await this.token.balanceOf(anotherAccount); 134 | assert.equal(recipientBalance, 100); 135 | }); 136 | 137 | describe('when trying to transfer when freezed', function () { 138 | beforeEach(async function () { 139 | await this.token.freeze(recipient, { from: owner }); 140 | }); 141 | 142 | it('revert when is sender', async function () { 143 | await assertRevert(this.token.transfer(anotherAccount, 100, { from: recipient })); 144 | }); 145 | 146 | it('revert when is receiver', async function () { 147 | await assertRevert(this.token.transfer(recipient, 0, { from: owner })); 148 | }); 149 | }); 150 | }); 151 | 152 | describe('approve', function () { 153 | it('allows to approve when unfreezed', async function () { 154 | await this.token.approve(approveAccount, 40, { from: recipient }); 155 | 156 | const allowance = await this.token.allowance(recipient, approveAccount); 157 | assert.equal(allowance, 40); 158 | }); 159 | 160 | it('allows to transfer when freezed and then unfreezed', async function () { 161 | await this.token.freeze(recipient, { from: owner }); 162 | await this.token.unfreeze(recipient, { from: owner }); 163 | 164 | await this.token.approve(approveAccount, 40, { from: recipient }); 165 | 166 | const allowance = await this.token.allowance(recipient, approveAccount); 167 | assert.equal(allowance, 40); 168 | }); 169 | 170 | describe('when trying to approve when freezed', function () { 171 | beforeEach(async function () { 172 | await this.token.freeze(recipient, { from: owner }); 173 | }); 174 | 175 | it('revert when is owner', async function () { 176 | await assertRevert(this.token.approve(approveAccount, 100, { from: recipient })); 177 | }); 178 | 179 | it('revert when is agent', async function () { 180 | await assertRevert(this.token.approve(recipient, 0, { from: owner })); 181 | }); 182 | }); 183 | }); 184 | 185 | describe('transfer from', function () { 186 | beforeEach(async function () { 187 | await this.token.approve(approveAccount, 50, { from: recipient }); 188 | }); 189 | 190 | it('allows to transfer from when unfreezed', async function () { 191 | await this.token.transferFrom(recipient, anotherAccount, 40, { from: approveAccount }); 192 | 193 | const senderBalance = await this.token.balanceOf(recipient); 194 | assert.equal(senderBalance, 60); 195 | 196 | const recipientBalance = await this.token.balanceOf(anotherAccount); 197 | assert.equal(recipientBalance, 40); 198 | }); 199 | 200 | it('allows to transfer when freezed the sender and then unfreezed', async function () { 201 | await this.token.freeze(recipient, { from: owner }); 202 | await this.token.unfreeze(recipient, { from: owner }); 203 | 204 | await this.token.transferFrom(recipient, anotherAccount, 40, { from: approveAccount }); 205 | 206 | const senderBalance = await this.token.balanceOf(recipient); 207 | assert.equal(senderBalance, 60); 208 | 209 | const recipientBalance = await this.token.balanceOf(anotherAccount); 210 | assert.equal(recipientBalance, 40); 211 | }); 212 | 213 | it('allows to transfer when freezed the receiver and then unfreezed', async function () { 214 | await this.token.freeze(anotherAccount, { from: owner }); 215 | await this.token.unfreeze(anotherAccount, { from: owner }); 216 | 217 | await this.token.transferFrom(recipient, anotherAccount, 40, { from: approveAccount }); 218 | 219 | const senderBalance = await this.token.balanceOf(recipient); 220 | assert.equal(senderBalance, 60); 221 | 222 | const recipientBalance = await this.token.balanceOf(anotherAccount); 223 | assert.equal(recipientBalance, 40); 224 | }); 225 | 226 | describe('when trying to transfer from when freezed the sender', function () { 227 | beforeEach(async function () { 228 | await this.token.freeze(recipient, { from: owner }); 229 | }); 230 | 231 | it('revert', async function () { 232 | await assertRevert(this.token.transferFrom(recipient, anotherAccount, 40, { from: approveAccount })); 233 | }); 234 | }); 235 | 236 | describe('when trying to transfer from when freezed the receiver', function () { 237 | beforeEach(async function () { 238 | await this.token.freeze(anotherAccount, { from: owner }); 239 | }); 240 | 241 | it('revert', async function () { 242 | await assertRevert(this.token.transferFrom(recipient, anotherAccount, 40, { from: approveAccount })); 243 | }); 244 | }); 245 | }); 246 | 247 | describe('decrease approval', function () { 248 | beforeEach(async function () { 249 | await this.token.approve(approveAccount, 100, { from: recipient }); 250 | }); 251 | 252 | it('allows to decrease approval when unfreezed', async function () { 253 | await this.token.decreaseApproval(approveAccount, 40, { from: recipient }); 254 | 255 | const allowance = await this.token.allowance(recipient, approveAccount); 256 | assert.equal(allowance, 60); 257 | }); 258 | 259 | it('allows to decrease approval when freezed and then unfreezed', async function () { 260 | await this.token.freeze(recipient, { from: owner }); 261 | await this.token.unfreeze(recipient, { from: owner }); 262 | 263 | await this.token.decreaseApproval(approveAccount, 40, { from: recipient }); 264 | 265 | const allowance = await this.token.allowance(recipient, approveAccount); 266 | assert.equal(allowance, 60); 267 | }); 268 | 269 | describe('when trying to decrease approval when freezed', function () { 270 | beforeEach(async function () { 271 | await this.token.freeze(recipient, { from: owner }); 272 | }); 273 | 274 | it('revert when is owner', async function () { 275 | await assertRevert(this.token.decreaseApproval(approveAccount, 40, { from: recipient })); 276 | }); 277 | 278 | it('revert when is agent', async function () { 279 | await assertRevert(this.token.decreaseApproval(recipient, 0, { from: owner })); 280 | }); 281 | }); 282 | }); 283 | 284 | describe('increase approval', function () { 285 | beforeEach(async function () { 286 | await this.token.approve(approveAccount, 100, { from: recipient }); 287 | }); 288 | 289 | it('allows to increase approval when unfreezed', async function () { 290 | await this.token.increaseApproval(approveAccount, 40, { from: recipient }); 291 | 292 | const allowance = await this.token.allowance(recipient, approveAccount); 293 | assert.equal(allowance, 140); 294 | }); 295 | 296 | it('allows to increase approval when freezed and then unfreezed', async function () { 297 | await this.token.freeze(recipient, { from: owner }); 298 | await this.token.unfreeze(recipient, { from: owner }); 299 | 300 | await this.token.increaseApproval(approveAccount, 40, { from: recipient }); 301 | 302 | const allowance = await this.token.allowance(recipient, approveAccount); 303 | assert.equal(allowance, 140); 304 | }); 305 | 306 | describe('when trying to increase approval when freezed', function () { 307 | beforeEach(async function () { 308 | await this.token.freeze(recipient, { from: owner }); 309 | }); 310 | 311 | it('revert when is owner', async function () { 312 | await assertRevert(this.token.increaseApproval(approveAccount, 40, { from: recipient })); 313 | }); 314 | 315 | it('revert when is agent', async function () { 316 | await assertRevert(this.token.increaseApproval(recipient, 0, { from: owner })); 317 | }); 318 | }); 319 | }); 320 | }); 321 | }); 322 | -------------------------------------------------------------------------------- /test/StandardToken.test.js: -------------------------------------------------------------------------------- 1 | import assertRevert from './helpers/assertRevert'; 2 | const StandardTokenMock = artifacts.require('StandardTokenMock'); 3 | 4 | contract('StandardToken', function ([_, owner, recipient, anotherAccount]) { 5 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 6 | 7 | beforeEach(async function () { 8 | this.token = await StandardTokenMock.new(owner, 100); 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 | }); 48 | 49 | describe('when the sender has enough balance', function () { 50 | const amount = 100; 51 | 52 | it('transfers the requested amount', async function () { 53 | await this.token.transfer(to, amount, { from: owner }); 54 | 55 | const senderBalance = await this.token.balanceOf(owner); 56 | assert.equal(senderBalance, 0); 57 | 58 | const recipientBalance = await this.token.balanceOf(to); 59 | assert.equal(recipientBalance, amount); 60 | }); 61 | 62 | it('emits a transfer event', async function () { 63 | const { logs } = await this.token.transfer(to, amount, { from: owner }); 64 | 65 | assert.equal(logs.length, 1); 66 | assert.equal(logs[0].event, 'Transfer'); 67 | assert.equal(logs[0].args.from, owner); 68 | assert.equal(logs[0].args.to, to); 69 | assert(logs[0].args.value.eq(amount)); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('when the recipient is the zero address', function () { 75 | const to = ZERO_ADDRESS; 76 | 77 | it('reverts', async function () { 78 | await assertRevert(this.token.transfer(to, 100, { from: owner })); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('approve', function () { 84 | describe('when the agent is not the zero address', function () { 85 | const agent = recipient; 86 | 87 | describe('when the sender has enough balance', function () { 88 | const amount = 100; 89 | 90 | it('emits an approval event', async function () { 91 | const { logs } = await this.token.approve(agent, amount, { from: owner }); 92 | 93 | assert.equal(logs.length, 1); 94 | assert.equal(logs[0].event, 'Approval'); 95 | assert.equal(logs[0].args.owner, owner); 96 | assert.equal(logs[0].args.agent, agent); 97 | assert(logs[0].args.value.eq(amount)); 98 | }); 99 | 100 | describe('when there was no approved amount before', function () { 101 | it('approves the requested amount', async function () { 102 | await this.token.approve(agent, amount, { from: owner }); 103 | 104 | const allowance = await this.token.allowance(owner, agent); 105 | assert.equal(allowance, amount); 106 | }); 107 | }); 108 | 109 | describe('when the agent had an approved amount', function () { 110 | beforeEach(async function () { 111 | await this.token.approve(agent, 1, { from: owner }); 112 | }); 113 | 114 | it('approves the requested amount and replaces the previous one', async function () { 115 | await this.token.approve(agent, amount, { from: owner }); 116 | 117 | const allowance = await this.token.allowance(owner, agent); 118 | assert.equal(allowance, amount); 119 | }); 120 | }); 121 | }); 122 | 123 | describe('when the sender does not have enough balance', function () { 124 | const amount = 101; 125 | 126 | it('emits an approval event', async function () { 127 | const { logs } = await this.token.approve(agent, amount, { from: owner }); 128 | 129 | assert.equal(logs.length, 1); 130 | assert.equal(logs[0].event, 'Approval'); 131 | assert.equal(logs[0].args.owner, owner); 132 | assert.equal(logs[0].args.agent, agent); 133 | assert(logs[0].args.value.eq(amount)); 134 | }); 135 | 136 | describe('when there was no approved amount before', function () { 137 | it('approves the requested amount', async function () { 138 | await this.token.approve(agent, amount, { from: owner }); 139 | 140 | const allowance = await this.token.allowance(owner, agent); 141 | assert.equal(allowance, amount); 142 | }); 143 | }); 144 | 145 | describe('when the agent had an approved amount', function () { 146 | beforeEach(async function () { 147 | await this.token.approve(agent, 1, { from: owner }); 148 | }); 149 | 150 | it('approves the requested amount and replaces the previous one', async function () { 151 | await this.token.approve(agent, amount, { from: owner }); 152 | 153 | const allowance = await this.token.allowance(owner, agent); 154 | assert.equal(allowance, amount); 155 | }); 156 | }); 157 | }); 158 | }); 159 | 160 | describe('when the agent is the zero address', function () { 161 | const amount = 100; 162 | const agent = ZERO_ADDRESS; 163 | 164 | it('reverts', async function () { 165 | await assertRevert(this.token.approve(agent, amount, { from: owner })); 166 | }); 167 | }); 168 | }); 169 | 170 | describe('transfer from', function () { 171 | const agent = recipient; 172 | 173 | describe('when the recipient is not the zero address', function () { 174 | const to = anotherAccount; 175 | 176 | describe('when the agent has enough approved balance', function () { 177 | beforeEach(async function () { 178 | await this.token.approve(agent, 100, { from: owner }); 179 | }); 180 | 181 | describe('when the owner has enough balance', function () { 182 | const amount = 100; 183 | 184 | it('transfers the requested amount', async function () { 185 | await this.token.transferFrom(owner, to, amount, { from: agent }); 186 | 187 | const senderBalance = await this.token.balanceOf(owner); 188 | assert.equal(senderBalance, 0); 189 | 190 | const recipientBalance = await this.token.balanceOf(to); 191 | assert.equal(recipientBalance, amount); 192 | }); 193 | 194 | it('decreases the agent allowance', async function () { 195 | await this.token.transferFrom(owner, to, amount, { from: agent }); 196 | 197 | const allowance = await this.token.allowance(owner, agent); 198 | assert(allowance.eq(0)); 199 | }); 200 | 201 | it('emits a transfer event', async function () { 202 | const { logs } = await this.token.transferFrom(owner, to, amount, { from: agent }); 203 | 204 | assert.equal(logs.length, 1); 205 | assert.equal(logs[0].event, 'Transfer'); 206 | assert.equal(logs[0].args.from, owner); 207 | assert.equal(logs[0].args.to, to); 208 | assert(logs[0].args.value.eq(amount)); 209 | }); 210 | }); 211 | 212 | describe('when the owner does not have enough balance', function () { 213 | const amount = 101; 214 | 215 | it('reverts', async function () { 216 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 217 | }); 218 | }); 219 | }); 220 | 221 | describe('when the agent does not have enough approved balance', function () { 222 | beforeEach(async function () { 223 | await this.token.approve(agent, 99, { from: owner }); 224 | }); 225 | 226 | describe('when the owner has enough balance', function () { 227 | const amount = 100; 228 | 229 | it('reverts', async function () { 230 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 231 | }); 232 | }); 233 | 234 | describe('when the owner does not have enough balance', function () { 235 | const amount = 101; 236 | 237 | it('reverts', async function () { 238 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 239 | }); 240 | }); 241 | }); 242 | }); 243 | 244 | describe('when the recipient is the zero address', function () { 245 | const amount = 100; 246 | const to = ZERO_ADDRESS; 247 | 248 | beforeEach(async function () { 249 | await this.token.approve(agent, amount, { from: owner }); 250 | }); 251 | 252 | it('reverts', async function () { 253 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 254 | }); 255 | }); 256 | }); 257 | 258 | describe('decrease approval', function () { 259 | describe('when the agent is not the zero address', function () { 260 | const agent = recipient; 261 | 262 | describe('when the sender has enough balance', function () { 263 | const amount = 100; 264 | 265 | it('emits an approval event', async function () { 266 | const { logs } = await this.token.decreaseApproval(agent, amount, { from: owner }); 267 | 268 | assert.equal(logs.length, 1); 269 | assert.equal(logs[0].event, 'Approval'); 270 | assert.equal(logs[0].args.owner, owner); 271 | assert.equal(logs[0].args.agent, agent); 272 | assert(logs[0].args.value.eq(0)); 273 | }); 274 | 275 | describe('when there was no approved amount before', function () { 276 | it('keeps the allowance to zero', async function () { 277 | await this.token.decreaseApproval(agent, amount, { from: owner }); 278 | 279 | const allowance = await this.token.allowance(owner, agent); 280 | assert.equal(allowance, 0); 281 | }); 282 | }); 283 | 284 | describe('when the agent had an approved amount', function () { 285 | beforeEach(async function () { 286 | await this.token.approve(agent, amount + 1, { from: owner }); 287 | }); 288 | 289 | it('decreases the agent allowance subtracting the requested amount', async function () { 290 | await this.token.decreaseApproval(agent, amount, { from: owner }); 291 | 292 | const allowance = await this.token.allowance(owner, agent); 293 | assert.equal(allowance, 1); 294 | }); 295 | }); 296 | }); 297 | 298 | describe('when the sender does not have enough balance', function () { 299 | const amount = 101; 300 | 301 | it('emits an approval event', async function () { 302 | const { logs } = await this.token.decreaseApproval(agent, amount, { from: owner }); 303 | 304 | assert.equal(logs.length, 1); 305 | assert.equal(logs[0].event, 'Approval'); 306 | assert.equal(logs[0].args.owner, owner); 307 | assert.equal(logs[0].args.agent, agent); 308 | assert(logs[0].args.value.eq(0)); 309 | }); 310 | 311 | describe('when there was no approved amount before', function () { 312 | it('keeps the allowance to zero', async function () { 313 | await this.token.decreaseApproval(agent, amount, { from: owner }); 314 | 315 | const allowance = await this.token.allowance(owner, agent); 316 | assert.equal(allowance, 0); 317 | }); 318 | }); 319 | 320 | describe('when the agent had an approved amount', function () { 321 | beforeEach(async function () { 322 | await this.token.approve(agent, amount + 1, { from: owner }); 323 | }); 324 | 325 | it('decreases the agent allowance subtracting the requested amount', async function () { 326 | await this.token.decreaseApproval(agent, amount, { from: owner }); 327 | 328 | const allowance = await this.token.allowance(owner, agent); 329 | assert.equal(allowance, 1); 330 | }); 331 | }); 332 | }); 333 | }); 334 | 335 | describe('when the agent is the zero address', function () { 336 | const amount = 100; 337 | const agent = ZERO_ADDRESS; 338 | 339 | it('reverts', async function () { 340 | await assertRevert(this.token.decreaseApproval(agent, amount, { from: owner })); 341 | }); 342 | }); 343 | }); 344 | 345 | describe('increase approval', function () { 346 | const amount = 100; 347 | 348 | describe('when the agent is not the zero address', function () { 349 | const agent = recipient; 350 | 351 | describe('when the sender has enough balance', function () { 352 | it('emits an approval event', async function () { 353 | const { logs } = await this.token.increaseApproval(agent, amount, { from: owner }); 354 | 355 | assert.equal(logs.length, 1); 356 | assert.equal(logs[0].event, 'Approval'); 357 | assert.equal(logs[0].args.owner, owner); 358 | assert.equal(logs[0].args.agent, agent); 359 | assert(logs[0].args.value.eq(amount)); 360 | }); 361 | 362 | describe('when there was no approved amount before', function () { 363 | it('approves the requested amount', async function () { 364 | await this.token.increaseApproval(agent, amount, { from: owner }); 365 | 366 | const allowance = await this.token.allowance(owner, agent); 367 | assert.equal(allowance, amount); 368 | }); 369 | }); 370 | 371 | describe('when the agent had an approved amount', function () { 372 | beforeEach(async function () { 373 | await this.token.approve(agent, 1, { from: owner }); 374 | }); 375 | 376 | it('increases the agent allowance adding the requested amount', async function () { 377 | await this.token.increaseApproval(agent, amount, { from: owner }); 378 | 379 | const allowance = await this.token.allowance(owner, agent); 380 | assert.equal(allowance, amount + 1); 381 | }); 382 | }); 383 | }); 384 | 385 | describe('when the sender does not have enough balance', function () { 386 | const amount = 101; 387 | 388 | it('emits an approval event', async function () { 389 | const { logs } = await this.token.increaseApproval(agent, amount, { from: owner }); 390 | 391 | assert.equal(logs.length, 1); 392 | assert.equal(logs[0].event, 'Approval'); 393 | assert.equal(logs[0].args.owner, owner); 394 | assert.equal(logs[0].args.agent, agent); 395 | assert(logs[0].args.value.eq(amount)); 396 | }); 397 | 398 | describe('when there was no approved amount before', function () { 399 | it('approves the requested amount', async function () { 400 | await this.token.increaseApproval(agent, amount, { from: owner }); 401 | 402 | const allowance = await this.token.allowance(owner, agent); 403 | assert.equal(allowance, amount); 404 | }); 405 | }); 406 | 407 | describe('when the agent had an approved amount', function () { 408 | beforeEach(async function () { 409 | await this.token.approve(agent, 1, { from: owner }); 410 | }); 411 | 412 | it('increases the agent allowance adding the requested amount', async function () { 413 | await this.token.increaseApproval(agent, amount, { from: owner }); 414 | 415 | const allowance = await this.token.allowance(owner, agent); 416 | assert.equal(allowance, amount + 1); 417 | }); 418 | }); 419 | }); 420 | }); 421 | 422 | describe('when the agent is the zero address', function () { 423 | const agent = ZERO_ADDRESS; 424 | 425 | it('reverts', async function () { 426 | await assertRevert(this.token.increaseApproval(agent, amount, { from: owner })); 427 | }); 428 | }); 429 | }); 430 | }); 431 | -------------------------------------------------------------------------------- /test/DemoToken.test.js: -------------------------------------------------------------------------------- 1 | import assertRevert from './helpers/assertRevert'; 2 | const BigNumber = web3.BigNumber; 3 | const DemoToken = artifacts.require('DemoToken'); 4 | 5 | require('chai') 6 | .use(require('chai-bignumber')(BigNumber)) 7 | .should(); 8 | 9 | contract('DemoToken', function ([_, owner, recipient, anotherAccount]) { 10 | const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; 11 | const totalValue = new BigNumber(10000000000000000000000); 12 | beforeEach(async function () { 13 | this.token = await DemoToken.new(); 14 | await this.token.transfer(owner, 100); 15 | }); 16 | 17 | describe('total supply', function () { 18 | it('returns the total amount of tokens', async function () { 19 | const totalSupply = await this.token.totalSupply(); 20 | 21 | totalSupply.should.be.bignumber.equal(totalValue); 22 | }); 23 | }); 24 | 25 | describe('balanceOf', function () { 26 | describe('when the requested account has no tokens', function () { 27 | it('returns zero', async function () { 28 | const balance = await this.token.balanceOf(anotherAccount); 29 | 30 | assert.equal(balance, 0); 31 | }); 32 | }); 33 | 34 | describe('when the requested account has some tokens', function () { 35 | it('returns the total amount of tokens', async function () { 36 | const balance = await this.token.balanceOf(owner); 37 | 38 | assert.equal(balance, 100); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('transfer', function () { 44 | describe('when the recipient is not the zero address', function () { 45 | const to = recipient; 46 | 47 | describe('when the sender does not have enough balance', function () { 48 | const amount = 101; 49 | 50 | it('reverts', async function () { 51 | await assertRevert(this.token.transfer(to, amount, { from: owner })); 52 | }); 53 | }); 54 | 55 | describe('when the sender has enough balance', function () { 56 | const amount = 100; 57 | 58 | it('transfers the requested amount', async function () { 59 | await this.token.transfer(to, amount, { from: owner }); 60 | 61 | const senderBalance = await this.token.balanceOf(owner); 62 | assert.equal(senderBalance, 0); 63 | 64 | const recipientBalance = await this.token.balanceOf(to); 65 | assert.equal(recipientBalance, amount); 66 | }); 67 | 68 | it('emits a transfer event', async function () { 69 | const { logs } = await this.token.transfer(to, amount, { from: owner }); 70 | 71 | assert.equal(logs.length, 1); 72 | assert.equal(logs[0].event, 'Transfer'); 73 | assert.equal(logs[0].args.from, owner); 74 | assert.equal(logs[0].args.to, to); 75 | assert(logs[0].args.value.eq(amount)); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('when the recipient is the zero address', function () { 81 | const to = ZERO_ADDRESS; 82 | 83 | it('reverts', async function () { 84 | await assertRevert(this.token.transfer(to, 100, { from: owner })); 85 | }); 86 | }); 87 | }); 88 | 89 | describe('approve', function () { 90 | describe('when the agent is not the zero address', function () { 91 | const agent = recipient; 92 | 93 | describe('when the sender has enough balance', function () { 94 | const amount = 100; 95 | 96 | it('emits an approval event', async function () { 97 | const { logs } = await this.token.approve(agent, amount, { from: owner }); 98 | 99 | assert.equal(logs.length, 1); 100 | assert.equal(logs[0].event, 'Approval'); 101 | assert.equal(logs[0].args.owner, owner); 102 | assert.equal(logs[0].args.agent, agent); 103 | assert(logs[0].args.value.eq(amount)); 104 | }); 105 | 106 | describe('when there was no approved amount before', function () { 107 | it('approves the requested amount', async function () { 108 | await this.token.approve(agent, amount, { from: owner }); 109 | 110 | const allowance = await this.token.allowance(owner, agent); 111 | assert.equal(allowance, amount); 112 | }); 113 | }); 114 | 115 | describe('when the agent had an approved amount', function () { 116 | beforeEach(async function () { 117 | await this.token.approve(agent, 1, { from: owner }); 118 | }); 119 | 120 | it('approves the requested amount and replaces the previous one', async function () { 121 | await this.token.approve(agent, amount, { from: owner }); 122 | 123 | const allowance = await this.token.allowance(owner, agent); 124 | assert.equal(allowance, amount); 125 | }); 126 | }); 127 | }); 128 | 129 | describe('when the sender does not have enough balance', function () { 130 | const amount = 101; 131 | 132 | it('emits an approval event', async function () { 133 | const { logs } = await this.token.approve(agent, amount, { from: owner }); 134 | 135 | assert.equal(logs.length, 1); 136 | assert.equal(logs[0].event, 'Approval'); 137 | assert.equal(logs[0].args.owner, owner); 138 | assert.equal(logs[0].args.agent, agent); 139 | assert(logs[0].args.value.eq(amount)); 140 | }); 141 | 142 | describe('when there was no approved amount before', function () { 143 | it('approves the requested amount', async function () { 144 | await this.token.approve(agent, amount, { from: owner }); 145 | 146 | const allowance = await this.token.allowance(owner, agent); 147 | assert.equal(allowance, amount); 148 | }); 149 | }); 150 | 151 | describe('when the agent had an approved amount', function () { 152 | beforeEach(async function () { 153 | await this.token.approve(agent, 1, { from: owner }); 154 | }); 155 | 156 | it('approves the requested amount and replaces the previous one', async function () { 157 | await this.token.approve(agent, amount, { from: owner }); 158 | 159 | const allowance = await this.token.allowance(owner, agent); 160 | assert.equal(allowance, amount); 161 | }); 162 | }); 163 | }); 164 | }); 165 | 166 | describe('when the agent is the zero address', function () { 167 | const amount = 100; 168 | const agent = ZERO_ADDRESS; 169 | 170 | it('reverts', async function () { 171 | await assertRevert(this.token.approve(agent, amount, { from: owner })); 172 | }); 173 | }); 174 | }); 175 | 176 | describe('transfer from', function () { 177 | const agent = recipient; 178 | 179 | describe('when the recipient is not the zero address', function () { 180 | const to = anotherAccount; 181 | 182 | describe('when the agent has enough approved balance', function () { 183 | beforeEach(async function () { 184 | await this.token.approve(agent, 100, { from: owner }); 185 | }); 186 | 187 | describe('when the owner has enough balance', function () { 188 | const amount = 100; 189 | 190 | it('transfers the requested amount', async function () { 191 | await this.token.transferFrom(owner, to, amount, { from: agent }); 192 | 193 | const senderBalance = await this.token.balanceOf(owner); 194 | assert.equal(senderBalance, 0); 195 | 196 | const recipientBalance = await this.token.balanceOf(to); 197 | assert.equal(recipientBalance, amount); 198 | }); 199 | 200 | it('decreases the agent allowance', async function () { 201 | await this.token.transferFrom(owner, to, amount, { from: agent }); 202 | 203 | const allowance = await this.token.allowance(owner, agent); 204 | assert(allowance.eq(0)); 205 | }); 206 | 207 | it('emits a transfer event', async function () { 208 | const { logs } = await this.token.transferFrom(owner, to, amount, { from: agent }); 209 | 210 | assert.equal(logs.length, 1); 211 | assert.equal(logs[0].event, 'Transfer'); 212 | assert.equal(logs[0].args.from, owner); 213 | assert.equal(logs[0].args.to, to); 214 | assert(logs[0].args.value.eq(amount)); 215 | }); 216 | }); 217 | 218 | describe('when the owner does not have enough balance', function () { 219 | const amount = 101; 220 | 221 | it('reverts', async function () { 222 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 223 | }); 224 | }); 225 | }); 226 | 227 | describe('when the agent does not have enough approved balance', function () { 228 | beforeEach(async function () { 229 | await this.token.approve(agent, 99, { from: owner }); 230 | }); 231 | 232 | describe('when the owner has enough balance', function () { 233 | const amount = 100; 234 | 235 | it('reverts', async function () { 236 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 237 | }); 238 | }); 239 | 240 | describe('when the owner does not have enough balance', function () { 241 | const amount = 101; 242 | 243 | it('reverts', async function () { 244 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 245 | }); 246 | }); 247 | }); 248 | }); 249 | 250 | describe('when the recipient is the zero address', function () { 251 | const amount = 100; 252 | const to = ZERO_ADDRESS; 253 | 254 | beforeEach(async function () { 255 | await this.token.approve(agent, amount, { from: owner }); 256 | }); 257 | 258 | it('reverts', async function () { 259 | await assertRevert(this.token.transferFrom(owner, to, amount, { from: agent })); 260 | }); 261 | }); 262 | }); 263 | 264 | describe('decrease approval', function () { 265 | describe('when the agent is not the zero address', function () { 266 | const agent = recipient; 267 | 268 | describe('when the sender has enough balance', function () { 269 | const amount = 100; 270 | 271 | it('emits an approval event', async function () { 272 | const { logs } = await this.token.decreaseApproval(agent, amount, { from: owner }); 273 | 274 | assert.equal(logs.length, 1); 275 | assert.equal(logs[0].event, 'Approval'); 276 | assert.equal(logs[0].args.owner, owner); 277 | assert.equal(logs[0].args.agent, agent); 278 | assert(logs[0].args.value.eq(0)); 279 | }); 280 | 281 | describe('when there was no approved amount before', function () { 282 | it('keeps the allowance to zero', async function () { 283 | await this.token.decreaseApproval(agent, amount, { from: owner }); 284 | 285 | const allowance = await this.token.allowance(owner, agent); 286 | assert.equal(allowance, 0); 287 | }); 288 | }); 289 | 290 | describe('when the agent had an approved amount', function () { 291 | beforeEach(async function () { 292 | await this.token.approve(agent, amount + 1, { from: owner }); 293 | }); 294 | 295 | it('decreases the agent allowance subtracting the requested amount', async function () { 296 | await this.token.decreaseApproval(agent, amount, { from: owner }); 297 | 298 | const allowance = await this.token.allowance(owner, agent); 299 | assert.equal(allowance, 1); 300 | }); 301 | }); 302 | }); 303 | 304 | describe('when the sender does not have enough balance', function () { 305 | const amount = 101; 306 | 307 | it('emits an approval event', async function () { 308 | const { logs } = await this.token.decreaseApproval(agent, amount, { from: owner }); 309 | 310 | assert.equal(logs.length, 1); 311 | assert.equal(logs[0].event, 'Approval'); 312 | assert.equal(logs[0].args.owner, owner); 313 | assert.equal(logs[0].args.agent, agent); 314 | assert(logs[0].args.value.eq(0)); 315 | }); 316 | 317 | describe('when there was no approved amount before', function () { 318 | it('keeps the allowance to zero', async function () { 319 | await this.token.decreaseApproval(agent, amount, { from: owner }); 320 | 321 | const allowance = await this.token.allowance(owner, agent); 322 | assert.equal(allowance, 0); 323 | }); 324 | }); 325 | 326 | describe('when the agent had an approved amount', function () { 327 | beforeEach(async function () { 328 | await this.token.approve(agent, amount + 1, { from: owner }); 329 | }); 330 | 331 | it('decreases the agent allowance subtracting the requested amount', async function () { 332 | await this.token.decreaseApproval(agent, amount, { from: owner }); 333 | 334 | const allowance = await this.token.allowance(owner, agent); 335 | assert.equal(allowance, 1); 336 | }); 337 | }); 338 | }); 339 | }); 340 | 341 | describe('when the agent is the zero address', function () { 342 | const amount = 100; 343 | const agent = ZERO_ADDRESS; 344 | 345 | it('reverts', async function () { 346 | await assertRevert(this.token.decreaseApproval(agent, amount, { from: owner })); 347 | }); 348 | }); 349 | }); 350 | 351 | describe('increase approval', function () { 352 | const amount = 100; 353 | 354 | describe('when the agent is not the zero address', function () { 355 | const agent = recipient; 356 | 357 | describe('when the sender has enough balance', function () { 358 | it('emits an approval event', async function () { 359 | const { logs } = await this.token.increaseApproval(agent, amount, { from: owner }); 360 | 361 | assert.equal(logs.length, 1); 362 | assert.equal(logs[0].event, 'Approval'); 363 | assert.equal(logs[0].args.owner, owner); 364 | assert.equal(logs[0].args.agent, agent); 365 | assert(logs[0].args.value.eq(amount)); 366 | }); 367 | 368 | describe('when there was no approved amount before', function () { 369 | it('approves the requested amount', async function () { 370 | await this.token.increaseApproval(agent, amount, { from: owner }); 371 | 372 | const allowance = await this.token.allowance(owner, agent); 373 | assert.equal(allowance, amount); 374 | }); 375 | }); 376 | 377 | describe('when the agent had an approved amount', function () { 378 | beforeEach(async function () { 379 | await this.token.approve(agent, 1, { from: owner }); 380 | }); 381 | 382 | it('increases the agent allowance adding the requested amount', async function () { 383 | await this.token.increaseApproval(agent, amount, { from: owner }); 384 | 385 | const allowance = await this.token.allowance(owner, agent); 386 | assert.equal(allowance, amount + 1); 387 | }); 388 | }); 389 | }); 390 | 391 | describe('when the sender does not have enough balance', function () { 392 | const amount = 101; 393 | 394 | it('emits an approval event', async function () { 395 | const { logs } = await this.token.increaseApproval(agent, amount, { from: owner }); 396 | 397 | assert.equal(logs.length, 1); 398 | assert.equal(logs[0].event, 'Approval'); 399 | assert.equal(logs[0].args.owner, owner); 400 | assert.equal(logs[0].args.agent, agent); 401 | assert(logs[0].args.value.eq(amount)); 402 | }); 403 | 404 | describe('when there was no approved amount before', function () { 405 | it('approves the requested amount', async function () { 406 | await this.token.increaseApproval(agent, amount, { from: owner }); 407 | 408 | const allowance = await this.token.allowance(owner, agent); 409 | assert.equal(allowance, amount); 410 | }); 411 | }); 412 | 413 | describe('when the agent had an approved amount', function () { 414 | beforeEach(async function () { 415 | await this.token.approve(agent, 1, { from: owner }); 416 | }); 417 | 418 | it('increases the agent allowance adding the requested amount', async function () { 419 | await this.token.increaseApproval(agent, amount, { from: owner }); 420 | 421 | const allowance = await this.token.allowance(owner, agent); 422 | assert.equal(allowance, amount + 1); 423 | }); 424 | }); 425 | }); 426 | }); 427 | 428 | describe('when the agent is the zero address', function () { 429 | const agent = ZERO_ADDRESS; 430 | 431 | it('reverts', async function () { 432 | await assertRevert(this.token.increaseApproval(agent, amount, { from: owner })); 433 | }); 434 | }); 435 | }); 436 | }); 437 | --------------------------------------------------------------------------------