├── .gitignore ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── truffle.js ├── contracts ├── Migrations.sol ├── StarTokenInterface.sol └── AceToken.sol ├── README.md ├── package.json ├── test ├── utils.js └── acetoken.js └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | zeppelin-solidity/ 3 | node_modules/ -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | development: { 4 | host: "localhost", 5 | port: 8545, 6 | network_id: "*" // Match any network id 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var AceToken = artifacts.require("./AceToken.sol"); 2 | var SimpleAceVoting = artifacts.require("./SimpleAceVoting.sol"); 3 | 4 | module.exports = function(deployer) { 5 | deployer.deploy(AceToken).then(function() { 6 | return deployer.deploy(SimpleAceVoting, AceToken.address); 7 | }) 8 | }; 9 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.4; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACE Token 2 | 3 | ## Abstract 4 | First of one token for Token Stars platform 5 | 6 | ### Details 7 | __Name:__ ACE Token 8 | 9 | __Ticker:__ ACE 10 | 11 | __Decimals:__ 0 12 | 13 | ### Emission 14 | __Type:__ Once 15 | 16 | __Amount:__ Not more than *165,000,000* tokens 17 | 18 | __Distribution__ 19 | __2/3__ – crowdsale investors 20 | __1/6__ – team 21 | __1/6__ – community (rewards, bounty) 22 | 23 | ## Installation 24 | 1. Install (if not yet) node and npm on your machine 25 | 2. Execute command `npm install -g truffle` 26 | 3. Clone repository `git clone https://github.com/alerdenisov/ace-token.git ace` 27 | 28 | ## Usage 29 | 1. First run `truffle compile` 30 | 2. Then run `truffle migrate` to deploy the contracts onto your network of choice (default "development"). 31 | 32 | ## Testing 33 | 1. Run command `truffle test` 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ace-token", 3 | "version": "1.0.0", 4 | "description": "Implementation of Mintable ERC20 token based on Ethereum protocol", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "dependencies": { 9 | "zeppelin-solidity": "^1.2.0" 10 | }, 11 | "devDependencies": { 12 | "sol-merger": "0.0.2" 13 | }, 14 | "scripts": { 15 | "merge": "sol-merger ./contracts/AceToken.sol ./build/contracts/AceToken.sol" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://alerdenisov@bitbucket.org/alerdenisov/acetoken.git" 20 | }, 21 | "keywords": [ 22 | "ethereum", 23 | "eth", 24 | "solidity", 25 | "token", 26 | "erc20" 27 | ], 28 | "author": "Aler Denisov ", 29 | "license": "MIT", 30 | "homepage": "https://bitbucket.org/alerdenisov/acetoken#readme" 31 | } 32 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | expectThrow: async (promise, msg) => { 3 | let result 4 | try { 5 | result = await promise 6 | } catch (error) { 7 | const invalidJump = error.message.search('invalid JUMP') >= 0 8 | const invalidOpcode = error.message.search('invalid opcode') >= 0 9 | const outOfGas = error.message.search('out of gas') >= 0 10 | assert(invalidJump || invalidOpcode || outOfGas, "Expected throw, got '" + error + "' instead") 11 | return 12 | } 13 | 14 | if(typeof msg === 'string') { 15 | assert.fail(msg) 16 | } else if (typeof msg === 'function') { 17 | assert.fail(msg(result)) 18 | } else { 19 | assert.fail('Expected throw not received') 20 | } 21 | }, 22 | promisify: (inner) => new Promise((resolve, reject) => 23 | inner((err, res) => { 24 | if (err) { reject(err) } 25 | resolve(res); 26 | }) 27 | ) 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Aler Denisov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /contracts/StarTokenInterface.sol: -------------------------------------------------------------------------------- 1 | // ACE Token is a first token of Token Stars platform 2 | 3 | // Copyright (c) 2017 Aler Denisov 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 | 23 | pragma solidity ^0.4.11; 24 | 25 | import 'zeppelin-solidity/contracts/token/MintableToken.sol'; 26 | 27 | 28 | contract StarTokenInterface is MintableToken { 29 | // Cheatsheet of inherit methods and events 30 | // function transferOwnership(address newOwner); 31 | // function allowance(address owner, address spender) constant returns (uint256); 32 | // function transfer(address _to, uint256 _value) returns (bool); 33 | // function transferFrom(address from, address to, uint256 value) returns (bool); 34 | // function approve(address spender, uint256 value) returns (bool); 35 | // function increaseApproval (address _spender, uint _addedValue) returns (bool success); 36 | // function decreaseApproval (address _spender, uint _subtractedValue) returns (bool success); 37 | // function finishMinting() returns (bool); 38 | // function mint(address _to, uint256 _amount) returns (bool); 39 | // event Approval(address indexed owner, address indexed spender, uint256 value); 40 | // event Mint(address indexed to, uint256 amount); 41 | // event MintFinished(); 42 | 43 | // Custom methods and events 44 | function toggleTransfer() returns (bool); 45 | function toggleTransferFor(address _for) returns (bool); 46 | event ToggleTransferAllowance(bool state); 47 | event ToggleTransferAllowanceFor(address indexed who, bool state); 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /contracts/AceToken.sol: -------------------------------------------------------------------------------- 1 | // ACE Token is a first token of Token Stars platform 2 | 3 | // Copyright (c) 2017 Aler Denisov 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 | 23 | pragma solidity ^0.4.11; 24 | 25 | import 'zeppelin-solidity/contracts/math/SafeMath.sol'; 26 | import './StarTokenInterface.sol'; 27 | 28 | 29 | contract AceToken is StarTokenInterface { 30 | using SafeMath for uint256; 31 | 32 | // ERC20 constants 33 | string public constant name = "ACE Token"; 34 | string public constant symbol = "ACE"; 35 | uint public constant decimals = 0; 36 | 37 | // Minting constants 38 | uint256 public constant MAXSOLD_SUPPLY = 99000000; 39 | uint256 public constant HARDCAPPED_SUPPLY = 165000000; 40 | 41 | // Transfer rules 42 | bool public transferAllowed = false; 43 | mapping (address=>bool) public specialAllowed; 44 | 45 | // Transfer rules events 46 | event ToggleTransferAllowance(bool state); 47 | event ToggleTransferAllowanceFor(address indexed who, bool state); 48 | 49 | /** 50 | * @dev check transfer is allowed 51 | */ 52 | modifier allowTransfer() { 53 | require(transferAllowed || specialAllowed[msg.sender]); 54 | _; 55 | } 56 | 57 | /** 58 | * @dev Doesn't allow to send funds on contract! 59 | */ 60 | function () payable { 61 | require(false); 62 | } 63 | 64 | /** 65 | * @dev transfer token for a specified address if transfer is open 66 | * @param _to The address to transfer to. 67 | * @param _value The amount to be transferred. 68 | */ 69 | function transfer(address _to, uint256 _value) allowTransfer returns (bool) { 70 | return super.transfer(_to, _value); 71 | } 72 | 73 | 74 | /** 75 | * @dev Transfer tokens from one address to another if transfer is open 76 | * @param _from address The address which you want to send tokens from 77 | * @param _to address The address which you want to transfer to 78 | * @param _value uint256 the amount of tokens to be transferred 79 | */ 80 | function transferFrom(address _from, address _to, uint256 _value) allowTransfer returns (bool) { 81 | return super.transferFrom(_from, _to, _value); 82 | } 83 | 84 | /** 85 | * @dev Change current state of transfer allowence to opposite 86 | */ 87 | function toggleTransfer() onlyOwner returns (bool) { 88 | transferAllowed = !transferAllowed; 89 | ToggleTransferAllowance(transferAllowed); 90 | return transferAllowed; 91 | } 92 | 93 | /** 94 | * @dev allow transfer for the given address against global rules 95 | * @param _for addres The address of special allowed transfer (required for smart contracts) 96 | */ 97 | function toggleTransferFor(address _for) onlyOwner returns (bool) { 98 | specialAllowed[_for] = !specialAllowed[_for]; 99 | ToggleTransferAllowanceFor(_for, specialAllowed[_for]); 100 | return specialAllowed[_for]; 101 | } 102 | 103 | /** 104 | * @dev Function to mint tokens for investor 105 | * @param _to The address that will receive the minted tokens. 106 | * @param _amount The amount of tokens to emit. 107 | * @return A boolean that indicates if the operation was successful. 108 | */ 109 | function mint(address _to, uint256 _amount) onlyOwner canMint returns (bool) { 110 | require(_amount > 0); 111 | 112 | // create 2 extra token for each 3 sold 113 | uint256 extra = _amount.div(3).mul(2); 114 | uint256 total = _amount.add(extra); 115 | 116 | totalSupply = totalSupply.add(total); 117 | 118 | // Prevent to emit more than handcap! 119 | assert(totalSupply <= HARDCAPPED_SUPPLY); 120 | 121 | balances[_to] = balances[_to].add(_amount); 122 | balances[owner] = balances[owner].add(extra); 123 | 124 | Mint(_to, _amount); 125 | Mint(owner, extra); 126 | 127 | Transfer(0x0, _to, _amount); 128 | Transfer(0x0, owner, extra); 129 | 130 | return true; 131 | } 132 | 133 | /** 134 | * @dev Increase approved amount to spend 135 | * @param _spender The address which will spend the funds. 136 | * @param _addedValue The amount of tokens to increase already approved amount. 137 | */ 138 | function increaseApproval (address _spender, uint _addedValue) returns (bool success) { 139 | allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue); 140 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 141 | return true; 142 | } 143 | 144 | /** 145 | * @dev Decrease approved amount to spend 146 | * @param _spender The address which will spend the funds. 147 | * @param _subtractedValue The amount of tokens to decrease already approved amount. 148 | */ 149 | function decreaseApproval (address _spender, uint _subtractedValue) returns (bool success) { 150 | uint oldValue = allowed[msg.sender][_spender]; 151 | if (_subtractedValue > oldValue) { 152 | allowed[msg.sender][_spender] = 0; 153 | } else { 154 | allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); 155 | } 156 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 157 | return true; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /test/acetoken.js: -------------------------------------------------------------------------------- 1 | const expectThrow = require('./utils').expectThrow 2 | const promisify = require('./utils').promisify 3 | const AceToken = artifacts.require("./AceToken.sol"); 4 | 5 | 6 | function getBalance (account, at) { 7 | return promisify(cb => web3.eth.getBalance(account, at, cb)); 8 | } 9 | 10 | function sendTransaction(data) { 11 | return promisify(cb => web3.eth.sendTransaction(data, cb)); 12 | } 13 | 14 | let ACE 15 | 16 | contract('AceToken', accounts => { 17 | const OWNER_SIGNATURE = { from: accounts[0] } 18 | 19 | beforeEach(async() => { 20 | ACE = await AceToken.new(OWNER_SIGNATURE) 21 | }) 22 | 23 | describe('Payable', () => { 24 | it('should forward received funds to the owner', async() => { 25 | console.log(await getBalance(accounts[2])) 26 | console.log(await getBalance(accounts[0])) 27 | 28 | console.log(accounts[2]) 29 | console.log(ACE.address) 30 | 31 | expectThrow(sendTransaction({ 32 | from: accounts[2], 33 | to: ACE.address, 34 | value: web3.toWei(0.2, "ether") 35 | })) 36 | 37 | console.log(await getBalance(accounts[2])) 38 | console.log(await getBalance(accounts[0])) 39 | }) 40 | }) 41 | 42 | describe('Ownership', () => { 43 | it('should have a owner', async() => { 44 | const owner = await ACE.owner() 45 | assert.strictEqual(owner, accounts[0]) 46 | }) 47 | it('should transfer owner when it needed', async() => { 48 | await ACE.transferOwnership(accounts[1], OWNER_SIGNATURE) 49 | assert.strictEqual(await ACE.owner.call(), accounts[1]) 50 | }) 51 | 52 | it('should throw then not owner try to transfer ownership', async() => { 53 | return expectThrow(ACE.transferOwnership(accounts[2], { from: accounts[1] })) 54 | }) 55 | }) 56 | 57 | describe('Emission Tests', () => { 58 | const assertSupply = async (expect, msg) => { 59 | const supply = await ACE.totalSupply() 60 | if(msg) { 61 | assert(supply.equals(expect), msg) 62 | } else { 63 | assert(supply.equals(expect), `amount of supply isnt expected! (current is ${supply} but expected is ${expect})`) 64 | } 65 | } 66 | 67 | it('should starts with zero supply', async() => { 68 | return assertSupply(0, 'supply isnt zero') 69 | }) 70 | 71 | it('should create extra 2 tokens for each 3 tokens', async() => { 72 | await ACE.mint(accounts[1], 3, OWNER_SIGNATURE) 73 | await assertSupply(3+2) // 3 token allocated for account1 and few extra for a team and bounty 74 | }) 75 | 76 | it('should create extra tokens only for each 3 tokens', async() => { 77 | await ACE.mint(accounts[1], 7, OWNER_SIGNATURE) 78 | await assertSupply(7+4) 79 | }) 80 | 81 | it('should allocate huge amount', async() => { 82 | await ACE.mint(accounts[2], 60000000, OWNER_SIGNATURE) 83 | await assertSupply(100000000) 84 | }) 85 | 86 | it('prevent to mint more than hard cap', async() => { 87 | await ACE.mint(accounts[3], await ACE.MAXSOLD_SUPPLY(), OWNER_SIGNATURE) 88 | await expectThrow(ACE.mint(accounts[2], 10000, OWNER_SIGNATURE)) 89 | await assertSupply(await ACE.HARDCAPPED_SUPPLY()) 90 | }) 91 | }) 92 | 93 | describe('Token Manipulations', () => { 94 | async function assertBalance(account, expect) { 95 | const balance = await ACE.balanceOf.call(account) 96 | assert(balance.equals(expect), `${account} balance isn't expected (current is ${balance}, but expected is ${expect}`) 97 | } 98 | 99 | beforeEach(async() => { 100 | await ACE.mint(accounts[1], 10000, OWNER_SIGNATURE) 101 | await ACE.mint(accounts[2], 2000, OWNER_SIGNATURE) 102 | 103 | await assertBalance(accounts[1], 10000) 104 | await assertBalance(accounts[2], 2000) 105 | }) 106 | 107 | describe('Transfers', () => { 108 | it('should throw on transfer', async() => { 109 | return expectThrow(ACE.transfer(accounts[2], 1000, { from: accounts[1] })) 110 | }) 111 | 112 | it('should throw on transfer also on owner', async() => { 113 | return expectThrow(ACE.transfer(accounts[1], 1000, OWNER_SIGNATURE)) 114 | }) 115 | 116 | it('should allow to transfer funds', async() => { 117 | await ACE.toggleTransfer(OWNER_SIGNATURE) 118 | return ACE.transfer(accounts[1], 1000, OWNER_SIGNATURE) 119 | }) 120 | 121 | it('should allow transfer for special account', async() => { 122 | await ACE.toggleTransferFor(accounts[1], OWNER_SIGNATURE) 123 | await ACE.transfer(accounts[2], 5000, { from: accounts[1] }) 124 | await assertBalance(accounts[1], 5000) 125 | await assertBalance(accounts[2], 7000) 126 | }) 127 | 128 | it('should close transfer for special account', async() => { 129 | await ACE.toggleTransferFor(accounts[1], OWNER_SIGNATURE) 130 | await ACE.transfer(accounts[2], 5000, { from: accounts[1] }) 131 | await assertBalance(accounts[1], 5000) 132 | await assertBalance(accounts[2], 7000) 133 | 134 | await ACE.toggleTransferFor(accounts[1], OWNER_SIGNATURE) 135 | await expectThrow(ACE.transfer(accounts[2], 5000, { from: accounts[1] })) 136 | await assertBalance(accounts[1], 5000) 137 | await assertBalance(accounts[2], 7000) 138 | }) 139 | }) 140 | 141 | describe('Allowance', () => { 142 | it('should prevent transfer allowed tokens', async() => { 143 | await ACE.approve(accounts[2], 5000, { from: accounts[1] }) 144 | await expectThrow(ACE.transferFrom(accounts[1], accounts[2], 5000, { from: accounts[2] }), 145 | 'transfer should throw exception before transfer open') 146 | await assertBalance(accounts[1], 10000) 147 | await assertBalance(accounts[2], 2000) 148 | }) 149 | 150 | it('should transfer tokens as always', async() => { 151 | await ACE.approve(accounts[2], 5000, { from: accounts[1] }) 152 | await ACE.toggleTransfer(OWNER_SIGNATURE) 153 | await ACE.transferFrom(accounts[1], accounts[2], 5000, { from: accounts[2] }) 154 | await assertBalance(accounts[1], 5000) 155 | await assertBalance(accounts[2], 7000) 156 | }) 157 | }) 158 | }) 159 | }) --------------------------------------------------------------------------------