├── .gitignore ├── .gitattributes ├── src ├── .gitignore ├── migrations │ ├── 1_initial_migration.js │ └── 2_deploy_contracts.js ├── package.json ├── truffle.js ├── contracts │ ├── Migrations.sol │ ├── CHXVestingVaultFactory.sol │ ├── Whitelistable.sol │ ├── CHXVestingVault.sol │ ├── CHXSwap.sol │ ├── CHXTokenSale.sol │ └── CHXToken.sol ├── test │ ├── helpers.js │ ├── Whitelistable.js │ ├── CHXVestingVault.js │ ├── CHXSwap.js │ ├── CHXToken.js │ └── CHXTokenSale.js └── package-lock.json ├── docs ├── CHXTokenContractHierarchy.png ├── CHXTokenSwapIDEX.md ├── CHXTokenSwap.md └── CHXFunctionalSpecification.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | bin 4 | -------------------------------------------------------------------------------- /docs/CHXTokenContractHierarchy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnMarket/SmartContracts/HEAD/docs/CHXTokenContractHierarchy.png -------------------------------------------------------------------------------- /src/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function (deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chainium-smart-contracts", 3 | "version": "1.0.0", 4 | "description": "Chainium Smart Contracts", 5 | "private": true, 6 | "devDependencies": { 7 | "truffle": "^4.1.0", 8 | "zeppelin-solidity": "^1.4.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var CHXToken = artifacts.require("./CHXToken.sol") 2 | var CHXTokenSale = artifacts.require("./CHXTokenSale.sol") 3 | var CHXVestingVaultFactory = artifacts.require("./CHXVestingVaultFactory.sol") 4 | 5 | module.exports = function (deployer) { 6 | deployer.deploy(CHXToken) 7 | deployer.deploy(CHXTokenSale) 8 | deployer.deploy(CHXVestingVaultFactory) 9 | } 10 | -------------------------------------------------------------------------------- /src/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | /* 3 | solc: { 4 | optimizer: { 5 | enabled: true, 6 | runs: 200 7 | } 8 | }, 9 | networks: { 10 | development: { 11 | host: "localhost", 12 | port: 8545, 13 | network_id: "*", // Match any network id 14 | gas: 6700000 15 | } 16 | } 17 | */ 18 | }; 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Own Smart Contracts 2 | 3 | This repository contains smart contract code for [Own](https://www.weown.com) Token (CHX). 4 | 5 | [Functional Specification](docs/CHXFunctionalSpecification.md) document contains more details about smart contract functionality. 6 | 7 | 8 | ## Setup 9 | 10 | Global prerequisites: 11 | 12 | ``` 13 | $ sudo npm install -g truffle 14 | ``` 15 | 16 | Repository: 17 | 18 | ``` 19 | $ git clone https://github.com/OwnMarket/SmartContracts.git SmartContracts 20 | $ cd ./SmartContracts/src 21 | $ npm install 22 | ``` 23 | 24 | ## Running Tests 25 | 26 | ``` 27 | $ truffle test 28 | ``` 29 | -------------------------------------------------------------------------------- /src/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/contracts/CHXVestingVaultFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 4 | import './CHXToken.sol'; 5 | import './CHXVestingVault.sol'; 6 | 7 | contract CHXVestingVaultFactory is Ownable { 8 | CHXToken public tokenContract; 9 | address[] public vestingVaults; 10 | 11 | function CHXVestingVaultFactory() 12 | public 13 | { 14 | } 15 | 16 | function setTokenContract(address _tokenContractAddress) 17 | external 18 | onlyOwner 19 | { 20 | require(_tokenContractAddress != address(0)); 21 | tokenContract = CHXToken(_tokenContractAddress); 22 | } 23 | 24 | function createCHXVestingVault(address _beneficiary, uint _vestingTime) 25 | external 26 | { 27 | require(tokenContract != address(0)); 28 | vestingVaults.push(new CHXVestingVault(tokenContract, _beneficiary, _vestingTime)); 29 | } 30 | 31 | function numberOfVaultsCreated() 32 | public 33 | view 34 | returns(uint) 35 | { 36 | return vestingVaults.length; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Own AG 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 | -------------------------------------------------------------------------------- /src/contracts/Whitelistable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 4 | 5 | contract Whitelistable is Ownable { 6 | 7 | mapping (address => bool) whitelist; 8 | address public whitelistAdmin; 9 | 10 | function Whitelistable() 11 | public 12 | { 13 | whitelistAdmin = owner; // Owner fulfils the role of the admin initially, until new admin is set. 14 | } 15 | 16 | modifier onlyOwnerOrWhitelistAdmin() { 17 | require(msg.sender == owner || msg.sender == whitelistAdmin); 18 | _; 19 | } 20 | 21 | modifier onlyWhitelisted() { 22 | require(whitelist[msg.sender]); 23 | _; 24 | } 25 | 26 | function isWhitelisted(address _address) 27 | external 28 | view 29 | returns (bool) 30 | { 31 | return whitelist[_address]; 32 | } 33 | 34 | function addToWhitelist(address[] _addresses) 35 | external 36 | onlyOwnerOrWhitelistAdmin 37 | { 38 | for (uint i = 0; i < _addresses.length; i++) { 39 | whitelist[_addresses[i]] = true; 40 | } 41 | } 42 | 43 | function removeFromWhitelist(address[] _addresses) 44 | external 45 | onlyOwnerOrWhitelistAdmin 46 | { 47 | for (uint i = 0; i < _addresses.length; i++) { 48 | whitelist[_addresses[i]] = false; 49 | } 50 | } 51 | 52 | function setWhitelistAdmin(address _newAdmin) 53 | public 54 | onlyOwnerOrWhitelistAdmin 55 | { 56 | require(_newAdmin != address(0)); 57 | whitelistAdmin = _newAdmin; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/contracts/CHXVestingVault.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import './CHXToken.sol'; 4 | 5 | contract CHXVestingVault { 6 | CHXToken private tokenContract; 7 | address public beneficiary; 8 | uint public vestingTime; // Point in time (as UNIX timestamp) at which tokens are available for withdrawal. 9 | 10 | function CHXVestingVault(address _tokenContractAddress, address _beneficiary, uint _vestingTime) 11 | public 12 | { 13 | require(_tokenContractAddress != address(0)); 14 | require(_beneficiary != address(0)); 15 | require(_vestingTime > now); 16 | 17 | tokenContract = CHXToken(_tokenContractAddress); 18 | beneficiary = _beneficiary; 19 | vestingTime = _vestingTime; 20 | } 21 | 22 | modifier onlyBeneficiary() { 23 | require(msg.sender == beneficiary); 24 | _; 25 | } 26 | 27 | function changeBeneficiary(address _newBeneficiary) 28 | external 29 | onlyBeneficiary 30 | { 31 | beneficiary = _newBeneficiary; 32 | } 33 | 34 | function withdrawTokens() 35 | external 36 | onlyBeneficiary 37 | { 38 | require(vestingTime <= now); 39 | 40 | uint availableTokens = tokenContract.balanceOf(this); 41 | if (availableTokens > 0) { 42 | require(tokenContract.transfer(beneficiary, availableTokens)); 43 | } 44 | } 45 | 46 | // Enable recovery of ether sent by mistake to this contract's address. 47 | function drainStrayEther(uint _amount) 48 | external 49 | onlyBeneficiary 50 | returns (bool) 51 | { 52 | beneficiary.transfer(_amount); 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/contracts/CHXSwap.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 4 | import 'zeppelin-solidity/contracts/token/ERC20Basic.sol'; 5 | 6 | contract CHXSwap is Ownable { 7 | event AddressMapped(address indexed ethAddress, string chxAddress); 8 | event AddressMappingRemoved(address indexed ethAddress, string chxAddress); 9 | 10 | mapping (address => string) public mappedAddresses; 11 | 12 | function CHXSwap() 13 | public 14 | { 15 | } 16 | 17 | function mapAddress(string _chxAddress) 18 | external 19 | { 20 | address ethAddress = msg.sender; 21 | require(bytes(mappedAddresses[ethAddress]).length == 0); 22 | mappedAddresses[ethAddress] = _chxAddress; 23 | AddressMapped(ethAddress, _chxAddress); 24 | } 25 | 26 | function removeMappedAddress(address _ethAddress) 27 | external 28 | onlyOwner 29 | { 30 | require(bytes(mappedAddresses[_ethAddress]).length != 0); 31 | string memory chxAddress = mappedAddresses[_ethAddress]; 32 | delete mappedAddresses[_ethAddress]; 33 | AddressMappingRemoved(_ethAddress, chxAddress); 34 | } 35 | 36 | // Enable recovery of ether sent by mistake to this contract's address. 37 | function drainStrayEther(uint _amount) 38 | external 39 | onlyOwner 40 | returns (bool) 41 | { 42 | owner.transfer(_amount); 43 | return true; 44 | } 45 | 46 | // Enable recovery of any ERC20 compatible token sent by mistake to this contract's address. 47 | function drainStrayTokens(ERC20Basic _token, uint _amount) 48 | external 49 | onlyOwner 50 | returns (bool) 51 | { 52 | return _token.transfer(owner, _amount); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = module.exports || {} 2 | 3 | module.exports.e18 = x => web3.toBigNumber(10).pow(18).mul(x) 4 | 5 | module.exports.shouldFail = async promise => { 6 | try { 7 | await promise 8 | } catch (error) { 9 | const invalidOpcode = error.message.search('invalid opcode') >= 0 10 | const invalidJump = error.message.search('invalid JUMP') >= 0 11 | const outOfGas = error.message.search('out of gas') >= 0 12 | const revert = error.message.search('revert') >= 0 13 | 14 | assert(invalidOpcode || invalidJump || outOfGas || revert, "Expected throw, got '" + error + "' instead.") 15 | return 16 | } 17 | 18 | assert.fail('Expected throw not received') 19 | } 20 | 21 | module.exports.duration = { 22 | seconds: x => x, 23 | minutes: x => x * 60, 24 | hours: x => x * 60 * 60, 25 | days: x => x * 60 * 60 * 24, 26 | weeks: x => x * 60 * 60 * 24 * 7, 27 | years: x => x * 60 * 60 * 24 * 365 28 | } 29 | 30 | module.exports.lastEVMTime = () => web3.eth.getBlock('latest').timestamp 31 | 32 | module.exports.increaseEVMTime = async duration => { 33 | await web3.currentProvider.send({ 34 | jsonrpc: '2.0', 35 | method: 'evm_increaseTime', 36 | params: [duration], 37 | id: new Date().getSeconds() 38 | }) 39 | 40 | await web3.currentProvider.send({ 41 | jsonrpc: '2.0', 42 | method: 'evm_mine', 43 | params: [], 44 | id: new Date().getSeconds() 45 | }) 46 | } 47 | 48 | module.exports.increaseEVMTimeTo = newTime => { 49 | const currentTime = module.exports.lastEVMTime() 50 | if (newTime < currentTime) 51 | throw Error(`Cannot increase current time(${currentTime}) to a moment in the past(${newTime})`) 52 | return module.exports.increaseEVMTime(newTime - currentTime) 53 | } 54 | -------------------------------------------------------------------------------- /docs/CHXTokenSwapIDEX.md: -------------------------------------------------------------------------------- 1 | # CHX Token Swap for Balances Locked in IDEX Smart Contract 2 | 3 | If your ERC-20 CHX tokens are locked in IDEX, you can still perform the swap and get the native CHX allocated on Own blockchain MainNet. 4 | Process is the same as for regular swap. You need to map the native CHX address to the Ethereum address which has the ERC-20 CHX tokens locked in IDEX. 5 | 6 | - Please take a look at the [token swap instructions announcement article](https://medium.com/ownmarket/our-token-swap-and-public-mainnet-launch-is-approaching-heres-what-to-do-c49de8288ceb) for more information about the swap. 7 | 8 | - Follow the [detailed instructions](https://github.com/OwnMarket/SmartContracts/blob/master/docs/CHXTokenSwap.md). 9 | 10 | - You can also watch the entire process in [the video](https://www.youtube.com/watch?v=odV1KpsR-Vw). 11 | 12 | If you are not sure if your Ethereum address has tokens locked on IDEX, you can check that by following these steps: 13 | - Go to this URL: https://etherscan.io/address/0x2a0c0dbecc7e4d658f48e01e3fa353f44050c208#readContract 14 | - Go to section "11. balanceOf" 15 | - Enter CHX token contract address `0x1460a58096d80a50a2f1f956dda497611fa4f165` into "token (address)" field 16 | - Enter your Ethereum address in the "user (address)" field 17 | - Click "Query" button 18 | - If your address has any CHX balance locked in IDEX contract, that balance will appear under the button. 19 | 20 | **NOTE:** The balance in IDEX contract is not displayed as a decimal number. 18 rightmost digits represent the decimal fraction of the number. 21 | E.g. 1300000000000000000 is 1.3 CHX 22 | 23 | If the Ethereum address (which is holding your ERC-20 CHX balance in IDEX) already has a native CHX address mapped to it in the swap contract, 24 | then you don't need to repeat the process. Your IDEX balance will be allocated to the same CHX address. 25 | To check if you have already mapped the address: 26 | - Go to this URL: https://etherscan.io/address/0x59a8d0bdf9e024f060b230f8f54f291f4d03e2d5#readContract 27 | - Enter your ETH address in the "mappedAddresses" section and click "Query" button. 28 | - If there is a CHX address returned and shown below the button, then you don't need to repeat the process. 29 | -------------------------------------------------------------------------------- /src/test/Whitelistable.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js') 2 | const e18 = helpers.e18 3 | 4 | const CHXToken = artifacts.require('./CHXToken.sol') 5 | const CHXTokenSale = artifacts.require('./CHXTokenSale.sol') 6 | 7 | contract('Whitelistable', accounts => { 8 | const owner = accounts[0] 9 | const investor1 = accounts[1] 10 | const investor2 = accounts[2] 11 | const investor3 = accounts[3] 12 | const whitelistAdmin = accounts[9] 13 | 14 | let chxToken 15 | let chxTokenSale 16 | 17 | beforeEach(async () => { 18 | chxToken = chxToken || await CHXToken.deployed() 19 | chxTokenSale = chxTokenSale || await CHXTokenSale.deployed() 20 | }) 21 | 22 | it('initializes correctly', async () => { 23 | const initialWhitelistAdmin = await chxTokenSale.whitelistAdmin() 24 | assert.equal(initialWhitelistAdmin, owner, 'owner should be admin initially') 25 | assert.notEqual(initialWhitelistAdmin, whitelistAdmin, 'whitelistAdmin should not be admin initially') 26 | 27 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor1), 'Investor 1 should not be initially whitelisted') 28 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor2), 'Investor 2 should not be initially whitelisted') 29 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor3), 'Investor 3 should not be initially whitelisted') 30 | }) 31 | 32 | it('rejects attempts to change whitelist from non-admin address', async () => { 33 | // ACT 34 | await helpers.shouldFail( 35 | chxTokenSale.addToWhitelist([investor1, investor2, investor3], {from: whitelistAdmin})) 36 | 37 | // ASSERT 38 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor1), 'Investor 1 should not be whitelisted') 39 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor2), 'Investor 2 should not be whitelisted') 40 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor3), 'Investor 3 should not be whitelisted') 41 | }) 42 | 43 | it('can change whitelist admin', async () => { 44 | // ARRANGE 45 | assert.equal(await chxTokenSale.whitelistAdmin(), owner, 'owner should be admin initially') 46 | 47 | // ACT 48 | await chxTokenSale.setWhitelistAdmin(whitelistAdmin, {from: owner}) 49 | 50 | // ASSERT 51 | assert.equal(await chxTokenSale.whitelistAdmin(), whitelistAdmin, 'whitelistAdmin mismatch') 52 | }) 53 | 54 | it('can add addresses to the whitelist', async () => { 55 | // ARRANGE 56 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor1), 'Investor 1 should not be initially whitelisted') 57 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor2), 'Investor 2 should not be initially whitelisted') 58 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor3), 'Investor 3 should not be initially whitelisted') 59 | 60 | // ACT 61 | await chxTokenSale.addToWhitelist([investor1, investor2, investor3], {from: whitelistAdmin}) 62 | 63 | // ASSERT 64 | assert.isTrue(await chxTokenSale.isWhitelisted(investor1), 'Investor 1 should be whitelisted') 65 | assert.isTrue(await chxTokenSale.isWhitelisted(investor2), 'Investor 2 should be whitelisted') 66 | assert.isTrue(await chxTokenSale.isWhitelisted(investor3), 'Investor 3 should be whitelisted') 67 | }) 68 | 69 | it('can remove addresses from the whitelist', async () => { 70 | // ARRANGE 71 | assert.isTrue(await chxTokenSale.isWhitelisted(investor1), 'Investor 1 should be initially whitelisted') 72 | assert.isTrue(await chxTokenSale.isWhitelisted(investor2), 'Investor 2 should be initially whitelisted') 73 | assert.isTrue(await chxTokenSale.isWhitelisted(investor3), 'Investor 3 should be initially whitelisted') 74 | 75 | // ACT 76 | await chxTokenSale.removeFromWhitelist([investor1, investor3], {from: whitelistAdmin}) 77 | 78 | // ASSERT 79 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor1), 'Investor 1 should not be whitelisted') 80 | assert.isTrue(await chxTokenSale.isWhitelisted(investor2), 'Investor 2 should not be whitelisted') 81 | assert.isNotTrue(await chxTokenSale.isWhitelisted(investor3), 'Investor 3 should not be whitelisted') 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /src/contracts/CHXTokenSale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import 'zeppelin-solidity/contracts/math/SafeMath.sol'; 4 | import './Whitelistable.sol'; 5 | import './CHXToken.sol'; 6 | 7 | contract CHXTokenSale is Whitelistable { 8 | using SafeMath for uint; 9 | 10 | event TokenPurchased(address indexed investor, uint contribution, uint tokens); 11 | 12 | uint public constant TOKEN_PRICE = 170 szabo; // Assumes token has 18 decimals 13 | 14 | uint public saleStartTime; 15 | uint public saleEndTime; 16 | uint public maxGasPrice = 20e9 wei; // 20 GWEI - to prevent "gas race" 17 | uint public minContribution = 100 finney; // 0.1 ETH 18 | uint public maxContributionPhase1 = 500 finney; // 0.5 ETH 19 | uint public maxContributionPhase2 = 10 ether; 20 | uint public phase1DurationInHours = 24; 21 | 22 | CHXToken public tokenContract; 23 | 24 | mapping (address => uint) public etherContributions; 25 | mapping (address => uint) public tokenAllocations; 26 | uint public etherCollected; 27 | uint public tokensSold; 28 | 29 | function CHXTokenSale() 30 | public 31 | { 32 | } 33 | 34 | function setTokenContract(address _tokenContractAddress) 35 | external 36 | onlyOwner 37 | { 38 | require(_tokenContractAddress != address(0)); 39 | tokenContract = CHXToken(_tokenContractAddress); 40 | require(tokenContract.decimals() == 18); // Calculations assume 18 decimals (1 ETH = 10^18 WEI) 41 | } 42 | 43 | function transferOwnership(address newOwner) 44 | public 45 | onlyOwner 46 | { 47 | require(newOwner != owner); 48 | 49 | if (whitelistAdmin == owner) { 50 | setWhitelistAdmin(newOwner); 51 | } 52 | 53 | super.transferOwnership(newOwner); 54 | } 55 | 56 | 57 | //////////////////////////////////////////////////////////////////////////////////////////////////// 58 | // Sale 59 | //////////////////////////////////////////////////////////////////////////////////////////////////// 60 | 61 | function() 62 | public 63 | payable 64 | { 65 | address investor = msg.sender; 66 | uint contribution = msg.value; 67 | 68 | require(saleStartTime <= now && now <= saleEndTime); 69 | require(tx.gasprice <= maxGasPrice); 70 | require(whitelist[investor]); 71 | require(contribution >= minContribution); 72 | if (phase1DurationInHours.mul(1 hours).add(saleStartTime) >= now) { 73 | require(etherContributions[investor].add(contribution) <= maxContributionPhase1); 74 | } else { 75 | require(etherContributions[investor].add(contribution) <= maxContributionPhase2); 76 | } 77 | 78 | etherContributions[investor] = etherContributions[investor].add(contribution); 79 | etherCollected = etherCollected.add(contribution); 80 | 81 | uint multiplier = 1e18; // 18 decimal places 82 | uint tokens = contribution.mul(multiplier).div(TOKEN_PRICE); 83 | tokenAllocations[investor] = tokenAllocations[investor].add(tokens); 84 | tokensSold = tokensSold.add(tokens); 85 | 86 | require(tokenContract.transfer(investor, tokens)); 87 | TokenPurchased(investor, contribution, tokens); 88 | } 89 | 90 | function sendCollectedEther(address _recipient) 91 | external 92 | onlyOwner 93 | { 94 | if (this.balance > 0) { 95 | _recipient.transfer(this.balance); 96 | } 97 | } 98 | 99 | function sendRemainingTokens(address _recipient) 100 | external 101 | onlyOwner 102 | { 103 | uint unsoldTokens = tokenContract.balanceOf(this); 104 | if (unsoldTokens > 0) { 105 | require(tokenContract.transfer(_recipient, unsoldTokens)); 106 | } 107 | } 108 | 109 | 110 | //////////////////////////////////////////////////////////////////////////////////////////////////// 111 | // Configuration 112 | //////////////////////////////////////////////////////////////////////////////////////////////////// 113 | 114 | function setSaleTime(uint _newStartTime, uint _newEndTime) 115 | external 116 | onlyOwner 117 | { 118 | require(_newStartTime <= _newEndTime); 119 | saleStartTime = _newStartTime; 120 | saleEndTime = _newEndTime; 121 | } 122 | 123 | function setMaxGasPrice(uint _newMaxGasPrice) 124 | external 125 | onlyOwner 126 | { 127 | require(_newMaxGasPrice > 0); 128 | maxGasPrice = _newMaxGasPrice; 129 | } 130 | 131 | function setMinContribution(uint _newMinContribution) 132 | external 133 | onlyOwner 134 | { 135 | require(_newMinContribution > 0); 136 | minContribution = _newMinContribution; 137 | } 138 | 139 | function setMaxContributionPhase1(uint _newMaxContributionPhase1) 140 | external 141 | onlyOwner 142 | { 143 | require(_newMaxContributionPhase1 > minContribution); 144 | maxContributionPhase1 = _newMaxContributionPhase1; 145 | } 146 | 147 | function setMaxContributionPhase2(uint _newMaxContributionPhase2) 148 | external 149 | onlyOwner 150 | { 151 | require(_newMaxContributionPhase2 > minContribution); 152 | maxContributionPhase2 = _newMaxContributionPhase2; 153 | } 154 | 155 | function setPhase1DurationInHours(uint _newPhase1DurationInHours) 156 | external 157 | onlyOwner 158 | { 159 | require(_newPhase1DurationInHours > 0); 160 | phase1DurationInHours = _newPhase1DurationInHours; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/CHXVestingVault.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js') 2 | const e18 = helpers.e18 3 | 4 | const CHXToken = artifacts.require('./CHXToken.sol') 5 | const CHXVestingVault = artifacts.require('./CHXVestingVault.sol') 6 | const CHXVestingVaultFactory = artifacts.require('./CHXVestingVaultFactory.sol') 7 | 8 | contract('CHXVestingVault', accounts => { 9 | const owner = accounts[0] 10 | const beneficiary1 = accounts[5] 11 | const beneficiary2 = accounts[6] 12 | 13 | const tokens = e18(1234) 14 | 15 | let chxToken 16 | let chxVestingVaultFactory 17 | 18 | beforeEach(async () => { 19 | chxToken = chxToken || await CHXToken.deployed() 20 | chxVestingVaultFactory = chxVestingVaultFactory || await CHXVestingVaultFactory.deployed() 21 | }) 22 | 23 | it('initializes correctly', async () => { 24 | await chxVestingVaultFactory.setTokenContract(chxToken.address) 25 | assert.equal(await chxVestingVaultFactory.tokenContract(), chxToken.address, 26 | 'CHXVestingVaultFactory.tokenContract mismatch') 27 | 28 | await chxToken.setRestrictedState(false) // Enable transfers 29 | }) 30 | 31 | it('rejects creation of new CHXVestingVault with invalid beneficiary address', async () => { 32 | // ARRANGE 33 | const evmTime = web3.toBigNumber(helpers.lastEVMTime()) 34 | const vestingTime = evmTime.add(helpers.duration.days(1)) 35 | const vaultCountBefore = await chxVestingVaultFactory.numberOfVaultsCreated() 36 | assert(vaultCountBefore.equals(0)) 37 | 38 | // ACT 39 | await helpers.shouldFail(chxVestingVaultFactory.createCHXVestingVault(0x0, vestingTime)) 40 | 41 | // ASSERT 42 | const vaultCountAfter = await chxVestingVaultFactory.numberOfVaultsCreated() 43 | assert(vaultCountAfter.equals(vaultCountBefore), 'Vault should not have been created') 44 | }) 45 | 46 | it('rejects creation of new CHXVestingVault with time in the past', async () => { 47 | // ARRANGE 48 | const evmTime = web3.toBigNumber(helpers.lastEVMTime()) 49 | const vestingTime = evmTime.sub(helpers.duration.days(1)) 50 | const vaultCountBefore = await chxVestingVaultFactory.numberOfVaultsCreated() 51 | assert(vaultCountBefore.equals(0)) 52 | 53 | // ACT 54 | await helpers.shouldFail(chxVestingVaultFactory.createCHXVestingVault(beneficiary1, vestingTime)) 55 | 56 | // ASSERT 57 | const vaultCountAfter = await chxVestingVaultFactory.numberOfVaultsCreated() 58 | assert(vaultCountAfter.equals(vaultCountBefore), 'Vault should not have been created') 59 | }) 60 | 61 | it('creates new CHXVestingVault', async () => { 62 | // ARRANGE 63 | const evmTime = web3.toBigNumber(helpers.lastEVMTime()) 64 | const vestingTime = evmTime.add(helpers.duration.days(90)) 65 | const vaultCountBefore = await chxVestingVaultFactory.numberOfVaultsCreated() 66 | assert(vaultCountBefore.equals(0)) 67 | 68 | // ACT 69 | await chxVestingVaultFactory.createCHXVestingVault(beneficiary1, vestingTime) 70 | 71 | // ASSERT 72 | const vaultCountAfter = await chxVestingVaultFactory.numberOfVaultsCreated() 73 | assert(vaultCountAfter.equals(1)) 74 | 75 | const vaultAddress = await chxVestingVaultFactory.vestingVaults(0) 76 | assert.notEqual(vaultAddress, 0, 'Vesting vault not created') 77 | 78 | const vault = await CHXVestingVault.at(vaultAddress) 79 | assert.equal(await vault.beneficiary(), beneficiary1, 'beneficiary address mismatch') 80 | assert((await vault.vestingTime()).equals(vestingTime), 'vestingTime mismatch') 81 | }) 82 | 83 | it('locks the tokens sent to it', async () => { 84 | // ARRANGE 85 | const vaultAddress = await chxVestingVaultFactory.vestingVaults(0) 86 | const vault = await CHXVestingVault.at(vaultAddress) 87 | const vaultTokenBalanceBefore = await chxToken.balanceOf(vaultAddress) 88 | const beneficiary1TokenBalanceBefore = await chxToken.balanceOf(beneficiary1) 89 | 90 | // ACT 91 | await chxToken.transfer(vaultAddress, tokens) 92 | 93 | // ASSERT 94 | const vaultTokenBalanceAfter = await chxToken.balanceOf(vaultAddress) 95 | const beneficiary1TokenBalanceAfter = await chxToken.balanceOf(beneficiary1) 96 | 97 | assert(vaultTokenBalanceBefore.add(tokens).equals(vaultTokenBalanceAfter), 98 | 'Vault token balance mismatch') 99 | assert(beneficiary1TokenBalanceBefore.equals(beneficiary1TokenBalanceAfter), 100 | 'Beneficiary token balance mismatch') 101 | }) 102 | 103 | it('rejects token withdrawal before vesting time', async () => { 104 | // ARRANGE 105 | const vaultAddress = await chxVestingVaultFactory.vestingVaults(0) 106 | const vault = await CHXVestingVault.at(vaultAddress) 107 | 108 | // ACT 109 | await helpers.shouldFail(vault.withdrawTokens({from: beneficiary1})) 110 | }) 111 | 112 | it('accepts token withdrawal at vesting time', async () => { 113 | // ARRANGE 114 | const vaultAddress = await chxVestingVaultFactory.vestingVaults(0) 115 | const vault = await CHXVestingVault.at(vaultAddress) 116 | const vestingTime = await vault.vestingTime() 117 | await helpers.increaseEVMTimeTo(vestingTime) // Go to the future 118 | const vaultTokenBalanceBefore = await chxToken.balanceOf(vaultAddress) 119 | const beneficiary1TokenBalanceBefore = await chxToken.balanceOf(beneficiary1) 120 | 121 | // ACT 122 | await vault.withdrawTokens({from: beneficiary1}) 123 | 124 | // ASSERT 125 | const beneficiary1TokenBalanceAfter = await chxToken.balanceOf(beneficiary1) 126 | const vaultTokenBalanceAfter = await chxToken.balanceOf(vaultAddress) 127 | 128 | assert(beneficiary1TokenBalanceBefore.add(tokens).equals(beneficiary1TokenBalanceAfter), 129 | 'Beneficiary token balance mismatch') 130 | assert(vaultTokenBalanceBefore.sub(tokens).equals(vaultTokenBalanceAfter), 131 | 'Vault token balance mismatch') 132 | }) 133 | }) 134 | -------------------------------------------------------------------------------- /src/contracts/CHXToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.19; 2 | 3 | import 'zeppelin-solidity/contracts/token/BurnableToken.sol'; 4 | import 'zeppelin-solidity/contracts/ownership/Ownable.sol'; 5 | 6 | contract CHXToken is BurnableToken, Ownable { 7 | string public constant name = "Chainium"; 8 | string public constant symbol = "CHX"; 9 | uint8 public constant decimals = 18; 10 | 11 | bool public isRestricted = true; 12 | address public tokenSaleContractAddress; 13 | 14 | function CHXToken() 15 | public 16 | { 17 | totalSupply = 200000000e18; 18 | balances[owner] = totalSupply; 19 | Transfer(address(0), owner, totalSupply); 20 | } 21 | 22 | function setTokenSaleContractAddress(address _tokenSaleContractAddress) 23 | external 24 | onlyOwner 25 | { 26 | require(_tokenSaleContractAddress != address(0)); 27 | tokenSaleContractAddress = _tokenSaleContractAddress; 28 | } 29 | 30 | 31 | //////////////////////////////////////////////////////////////////////////////////////////////////// 32 | // Transfer Restriction 33 | //////////////////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | function setRestrictedState(bool _isRestricted) 36 | external 37 | onlyOwner 38 | { 39 | isRestricted = _isRestricted; 40 | } 41 | 42 | modifier restricted() { 43 | if (isRestricted) { 44 | require( 45 | msg.sender == owner || 46 | (msg.sender == tokenSaleContractAddress && tokenSaleContractAddress != address(0)) 47 | ); 48 | } 49 | _; 50 | } 51 | 52 | 53 | //////////////////////////////////////////////////////////////////////////////////////////////////// 54 | // Transfers 55 | //////////////////////////////////////////////////////////////////////////////////////////////////// 56 | 57 | function transfer(address _to, uint _value) 58 | public 59 | restricted 60 | returns (bool) 61 | { 62 | require(_to != address(this)); 63 | return super.transfer(_to, _value); 64 | } 65 | 66 | function transferFrom(address _from, address _to, uint _value) 67 | public 68 | restricted 69 | returns (bool) 70 | { 71 | require(_to != address(this)); 72 | return super.transferFrom(_from, _to, _value); 73 | } 74 | 75 | function approve(address _spender, uint _value) 76 | public 77 | restricted 78 | returns (bool) 79 | { 80 | return super.approve(_spender, _value); 81 | } 82 | 83 | function increaseApproval(address _spender, uint _addedValue) 84 | public 85 | restricted 86 | returns (bool success) 87 | { 88 | return super.increaseApproval(_spender, _addedValue); 89 | } 90 | 91 | function decreaseApproval(address _spender, uint _subtractedValue) 92 | public 93 | restricted 94 | returns (bool success) 95 | { 96 | return super.decreaseApproval(_spender, _subtractedValue); 97 | } 98 | 99 | 100 | //////////////////////////////////////////////////////////////////////////////////////////////////// 101 | // Batch transfers 102 | //////////////////////////////////////////////////////////////////////////////////////////////////// 103 | 104 | function batchTransfer(address[] _recipients, uint[] _values) 105 | external 106 | returns (bool) 107 | { 108 | require(_recipients.length == _values.length); 109 | 110 | for (uint i = 0; i < _values.length; i++) { 111 | require(transfer(_recipients[i], _values[i])); 112 | } 113 | 114 | return true; 115 | } 116 | 117 | function batchTransferFrom(address _from, address[] _recipients, uint[] _values) 118 | external 119 | returns (bool) 120 | { 121 | require(_recipients.length == _values.length); 122 | 123 | for (uint i = 0; i < _values.length; i++) { 124 | require(transferFrom(_from, _recipients[i], _values[i])); 125 | } 126 | 127 | return true; 128 | } 129 | 130 | function batchTransferFromMany(address[] _senders, address _to, uint[] _values) 131 | external 132 | returns (bool) 133 | { 134 | require(_senders.length == _values.length); 135 | 136 | for (uint i = 0; i < _values.length; i++) { 137 | require(transferFrom(_senders[i], _to, _values[i])); 138 | } 139 | 140 | return true; 141 | } 142 | 143 | function batchTransferFromManyToMany(address[] _senders, address[] _recipients, uint[] _values) 144 | external 145 | returns (bool) 146 | { 147 | require(_senders.length == _recipients.length); 148 | require(_senders.length == _values.length); 149 | 150 | for (uint i = 0; i < _values.length; i++) { 151 | require(transferFrom(_senders[i], _recipients[i], _values[i])); 152 | } 153 | 154 | return true; 155 | } 156 | 157 | function batchApprove(address[] _spenders, uint[] _values) 158 | external 159 | returns (bool) 160 | { 161 | require(_spenders.length == _values.length); 162 | 163 | for (uint i = 0; i < _values.length; i++) { 164 | require(approve(_spenders[i], _values[i])); 165 | } 166 | 167 | return true; 168 | } 169 | 170 | function batchIncreaseApproval(address[] _spenders, uint[] _addedValues) 171 | external 172 | returns (bool) 173 | { 174 | require(_spenders.length == _addedValues.length); 175 | 176 | for (uint i = 0; i < _addedValues.length; i++) { 177 | require(increaseApproval(_spenders[i], _addedValues[i])); 178 | } 179 | 180 | return true; 181 | } 182 | 183 | function batchDecreaseApproval(address[] _spenders, uint[] _subtractedValues) 184 | external 185 | returns (bool) 186 | { 187 | require(_spenders.length == _subtractedValues.length); 188 | 189 | for (uint i = 0; i < _subtractedValues.length; i++) { 190 | require(decreaseApproval(_spenders[i], _subtractedValues[i])); 191 | } 192 | 193 | return true; 194 | } 195 | 196 | 197 | //////////////////////////////////////////////////////////////////////////////////////////////////// 198 | // Miscellaneous 199 | //////////////////////////////////////////////////////////////////////////////////////////////////// 200 | 201 | function burn(uint _value) 202 | public 203 | onlyOwner 204 | { 205 | super.burn(_value); 206 | } 207 | 208 | // Enable recovery of ether sent by mistake to this contract's address. 209 | function drainStrayEther(uint _amount) 210 | external 211 | onlyOwner 212 | returns (bool) 213 | { 214 | owner.transfer(_amount); 215 | return true; 216 | } 217 | 218 | // Enable recovery of any ERC20 compatible token, sent by mistake to this contract's address. 219 | function drainStrayTokens(ERC20Basic _token, uint _amount) 220 | external 221 | onlyOwner 222 | returns (bool) 223 | { 224 | return _token.transfer(owner, _amount); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/test/CHXSwap.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js') 2 | 3 | const CHXSwap = artifacts.require('./CHXSwap.sol') 4 | 5 | contract('CHXSwap', accounts => { 6 | const admin = accounts[0] 7 | const ethAddress1 = accounts[1] 8 | const ethAddress2 = accounts[2] 9 | 10 | const chxAddress1 = "CH111111111111111111111111111111111" 11 | const chxAddress2 = "CH222222222222222222222222222222222" 12 | const chxAddress3 = "CH333333333333333333333333333333333" 13 | 14 | let chxSwap 15 | 16 | beforeEach(async () => { 17 | chxSwap = await CHXSwap.new() 18 | }) 19 | 20 | it('initializes correctly', async () => { 21 | assert.equal(await chxSwap.owner(), admin, 'Owner address mismatch') 22 | }) 23 | 24 | it('sets mapped address', async () => { 25 | // ARRANGE 26 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 27 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 28 | 29 | // ACT 30 | await chxSwap.mapAddress(chxAddress1, {from: ethAddress1}) 31 | 32 | // ASSERT 33 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 34 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 35 | 36 | assert.notEqual(chxAddress1After, chxAddress1Before, 'Mapped address 1 expected to change') 37 | assert.equal(chxAddress1After, chxAddress1, 'Mapped address 1 mismatch') 38 | 39 | assert.equal(chxAddress2After, chxAddress2Before, 'Mapped address 2 not expected to change') 40 | }) 41 | 42 | it('sets mapped address for all senders', async () => { 43 | // ARRANGE 44 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 45 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 46 | 47 | // ACT 48 | await chxSwap.mapAddress(chxAddress1, {from: ethAddress1}) 49 | await chxSwap.mapAddress(chxAddress2, {from: ethAddress2}) 50 | 51 | // ASSERT 52 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 53 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 54 | 55 | assert.notEqual(chxAddress1After, chxAddress1Before, 'Mapped address 1 expected to change') 56 | assert.equal(chxAddress1After, chxAddress1, 'Mapped address 1 mismatch') 57 | 58 | assert.notEqual(chxAddress2After, chxAddress2Before, 'Mapped address 2 expected to change') 59 | assert.equal(chxAddress2After, chxAddress2, 'Mapped address 2 mismatch') 60 | }) 61 | 62 | it('rejects address mapping if already exists', async () => { 63 | // ARRANGE 64 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 65 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 66 | 67 | await chxSwap.mapAddress(chxAddress1, {from: ethAddress1}) 68 | 69 | // ACT 70 | await helpers.shouldFail(chxSwap.mapAddress(chxAddress3, {from: ethAddress1})) 71 | 72 | // ASSERT 73 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 74 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 75 | 76 | assert.notEqual(chxAddress1After, chxAddress1Before, 'Mapped address 1 expected to change') 77 | assert.equal(chxAddress1After, chxAddress1, 'Mapped address 1 mismatch') 78 | 79 | assert.equal(chxAddress2After, chxAddress2Before, 'Mapped address 2 not expected to change') 80 | }) 81 | 82 | it('removes address mapping if called by contract owner', async () => { 83 | // ARRANGE 84 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 85 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 86 | 87 | await chxSwap.mapAddress(chxAddress1, {from: ethAddress1}) 88 | 89 | // ACT 90 | await chxSwap.removeMappedAddress(ethAddress1, {from: admin}) 91 | 92 | // ASSERT 93 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 94 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 95 | 96 | assert.equal(chxAddress1After, chxAddress1Before, 'Mapped address 1 not expected to change') 97 | assert.equal(chxAddress2After, chxAddress2Before, 'Mapped address 2 not expected to change') 98 | }) 99 | 100 | it('rejects address mapping removal if not called by contract owner', async () => { 101 | // ARRANGE 102 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 103 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 104 | 105 | await chxSwap.mapAddress(chxAddress1, {from: ethAddress1}) 106 | 107 | // ACT 108 | await helpers.shouldFail(chxSwap.removeMappedAddress(ethAddress1, {from: ethAddress1})) 109 | await helpers.shouldFail(chxSwap.removeMappedAddress(ethAddress1, {from: ethAddress2})) 110 | 111 | // ASSERT 112 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 113 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 114 | 115 | assert.notEqual(chxAddress1After, chxAddress1Before, 'Mapped address 1 expected to change') 116 | assert.equal(chxAddress1After, chxAddress1, 'Mapped address 1 mismatch') 117 | 118 | assert.equal(chxAddress2After, chxAddress2Before, 'Mapped address 2 not expected to change') 119 | }) 120 | 121 | it('rejects address mapping removal if address mapping does not exist', async () => { 122 | // ARRANGE 123 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 124 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 125 | 126 | // ACT 127 | await helpers.shouldFail(chxSwap.removeMappedAddress(ethAddress1, {from: admin})) 128 | 129 | // ASSERT 130 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 131 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 132 | 133 | assert.equal(chxAddress1After, chxAddress1Before, 'Mapped address 1 not expected to change') 134 | assert.equal(chxAddress2After, chxAddress2Before, 'Mapped address 2 not expected to change') 135 | }) 136 | 137 | it('accepts repeated address mapping if previously removed by contract owner', async () => { 138 | // ARRANGE 139 | const chxAddress1Before = await chxSwap.mappedAddresses(ethAddress1) 140 | const chxAddress2Before = await chxSwap.mappedAddresses(ethAddress2) 141 | 142 | await chxSwap.mapAddress(chxAddress1, {from: ethAddress1}) 143 | 144 | // ACT 145 | await chxSwap.removeMappedAddress(ethAddress1, {from: admin}) 146 | await chxSwap.mapAddress(chxAddress3, {from: ethAddress1}) 147 | 148 | // ASSERT 149 | const chxAddress1After = await chxSwap.mappedAddresses(ethAddress1) 150 | const chxAddress2After = await chxSwap.mappedAddresses(ethAddress2) 151 | 152 | assert.notEqual(chxAddress1After, chxAddress1Before, 'Mapped address 1 expected to change') 153 | assert.equal(chxAddress1After, chxAddress3, 'Mapped address 1 mismatch') 154 | 155 | assert.equal(chxAddress2After, chxAddress2Before, 'Mapped address 2 not expected to change') 156 | }) 157 | }) 158 | -------------------------------------------------------------------------------- /docs/CHXTokenSwap.md: -------------------------------------------------------------------------------- 1 | # CHX Token Swap 2 | 3 | Contributors in Own will know that we launched our native blockchain earlier this year. As of today, CHX tokens exist on the Ethereum blockchain. As part of this development, we are swapping the CHX ERC-20 tokens we were previously using to a CHX mainnet token on our Own blockchain. This document explains the process of swapping the CHX token. 4 | 5 | 6 | ## High Level Overview 7 | 8 | The CHX token swap process consists of three simple steps: 9 | 10 | 1. Create a new native CHX address using the [Own wallet](https://wallet.weown.com) on the Own blockchain network. 11 | 2. Submit your newly created native CHX address to the `CHXSwap` smart contract on the Ethereum network. 12 | 3. Wait for the tokens to be allocated to your CHX address on the Own blockchain network. 13 | 14 | These steps are explained in more detail below. If, at any point you are unsure what to do, please get in touch via our support email: support@weown.com. 15 | 16 | The entire process can be seen in the [video](https://www.youtube.com/watch?v=odV1KpsR-Vw) as well. 17 | 18 | 19 | ## Create the native CHX Address on the Own blockchain network 20 | 21 | To create a native CHX address, follow these simple steps: 22 | 23 | 1. Go to the [Own wallet](https://wallet.weown.com), which uses the Own blockchain network. 24 | 2. Click on the "Create wallet" menu item (on the left side navigation menu). 25 | 3. Write down and safely store your recovery phrase (a list of 24 words). Remember: **without the recovery phrase you will not be able to restore your wallet** if you ever need to. 26 | 4. Enter a secure password to encrypt your keystore file. You can use this file and the password as an alternative mechanism to restore and access the wallet without exposing your recovery phrase. (**The recovery phrase is still most important and a primary way to restore the wallet, so make sure you don't ever lose it.**) 27 | 5. Click on the "Create wallet" button. 28 | 6. Save your keystore file in safe location. 29 | 7. Take a note of the wallet address shown in the upper left corner, as you will need it in the following steps. 30 | 31 | 32 | ## Submit the native CHX Address into the Ethereum Token Swap Smart Contract 33 | 34 | 1. Go to ["Interact with Contract" section on MyEtherWallet](https://www.myetherwallet.com/interface/interact-with-contract). 35 | 2. Access your Ethereum wallet, which holds the ERC-20 CHX tokens, using one of the methods MyEtherWallet offers. 36 | 3. Enter the following value in "Contract Address" field: 37 | ``` 38 | 0x59a8D0bdF9e024f060B230F8F54f291F4D03e2D5 39 | ``` 40 | 4. Enter following text in "ABI/JSON Interface" field: 41 | ``` 42 | [ 43 | { 44 | "constant": true, 45 | "inputs": [], 46 | "name": "owner", 47 | "outputs": [ 48 | { 49 | "name": "", 50 | "type": "address" 51 | } 52 | ], 53 | "payable": false, 54 | "stateMutability": "view", 55 | "type": "function" 56 | }, 57 | { 58 | "constant": false, 59 | "inputs": [ 60 | { 61 | "name": "newOwner", 62 | "type": "address" 63 | } 64 | ], 65 | "name": "transferOwnership", 66 | "outputs": [], 67 | "payable": false, 68 | "stateMutability": "nonpayable", 69 | "type": "function" 70 | }, 71 | { 72 | "constant": true, 73 | "inputs": [ 74 | { 75 | "name": "", 76 | "type": "address" 77 | } 78 | ], 79 | "name": "mappedAddresses", 80 | "outputs": [ 81 | { 82 | "name": "", 83 | "type": "string" 84 | } 85 | ], 86 | "payable": false, 87 | "stateMutability": "view", 88 | "type": "function" 89 | }, 90 | { 91 | "inputs": [], 92 | "payable": false, 93 | "stateMutability": "nonpayable", 94 | "type": "constructor" 95 | }, 96 | { 97 | "anonymous": false, 98 | "inputs": [ 99 | { 100 | "indexed": true, 101 | "name": "ethAddress", 102 | "type": "address" 103 | }, 104 | { 105 | "indexed": false, 106 | "name": "chxAddress", 107 | "type": "string" 108 | } 109 | ], 110 | "name": "AddressMapped", 111 | "type": "event" 112 | }, 113 | { 114 | "anonymous": false, 115 | "inputs": [ 116 | { 117 | "indexed": true, 118 | "name": "ethAddress", 119 | "type": "address" 120 | }, 121 | { 122 | "indexed": false, 123 | "name": "chxAddress", 124 | "type": "string" 125 | } 126 | ], 127 | "name": "AddressMappingRemoved", 128 | "type": "event" 129 | }, 130 | { 131 | "anonymous": false, 132 | "inputs": [ 133 | { 134 | "indexed": true, 135 | "name": "previousOwner", 136 | "type": "address" 137 | }, 138 | { 139 | "indexed": true, 140 | "name": "newOwner", 141 | "type": "address" 142 | } 143 | ], 144 | "name": "OwnershipTransferred", 145 | "type": "event" 146 | }, 147 | { 148 | "constant": false, 149 | "inputs": [ 150 | { 151 | "name": "_chxAddress", 152 | "type": "string" 153 | } 154 | ], 155 | "name": "mapAddress", 156 | "outputs": [], 157 | "payable": false, 158 | "stateMutability": "nonpayable", 159 | "type": "function" 160 | }, 161 | { 162 | "constant": false, 163 | "inputs": [ 164 | { 165 | "name": "_ethAddress", 166 | "type": "address" 167 | } 168 | ], 169 | "name": "removeMappedAddress", 170 | "outputs": [], 171 | "payable": false, 172 | "stateMutability": "nonpayable", 173 | "type": "function" 174 | }, 175 | { 176 | "constant": false, 177 | "inputs": [ 178 | { 179 | "name": "_amount", 180 | "type": "uint256" 181 | } 182 | ], 183 | "name": "drainStrayEther", 184 | "outputs": [ 185 | { 186 | "name": "", 187 | "type": "bool" 188 | } 189 | ], 190 | "payable": false, 191 | "stateMutability": "nonpayable", 192 | "type": "function" 193 | }, 194 | { 195 | "constant": false, 196 | "inputs": [ 197 | { 198 | "name": "_token", 199 | "type": "address" 200 | }, 201 | { 202 | "name": "_amount", 203 | "type": "uint256" 204 | } 205 | ], 206 | "name": "drainStrayTokens", 207 | "outputs": [ 208 | { 209 | "name": "", 210 | "type": "bool" 211 | } 212 | ], 213 | "payable": false, 214 | "stateMutability": "nonpayable", 215 | "type": "function" 216 | } 217 | ] 218 | ``` 219 | 5. Click the "Continue" button. 220 | 6. Click the "Select an item" drop down button (to select the action from the contract) and choose `mapAddress` from the list. 221 | 7. Enter your native CHX address into the `_chxAddress` field. (No need to enter "Value in ETH") 222 | 8. Click the "Write" button. 223 | 9. Click the "Confirm and Send" button in the confirmation dialog. 224 | 10. Click the "Check Status on etherscan.io" button, to follow the status of the transaction. 225 | 226 | Once the transaction is processed and you see the status `Success`, your part of the swap process is completed. 227 | 228 | ### A few important notes: 229 | 230 | - The swap process will not require you to ever reveal your private key to anyone. You will only be required to sign a transaction for submitting your native CHX address into the smart contract. This is done via the above steps and your MyEtherWallet transaction. **We will never ask you for your private key – do not share or reveal this to anyone.** 231 | - The swap process does not involve you sending ERC-20 CHX tokens or ETH to any address. **We will never ask you to send any of your tokens to an ETH address.** 232 | - The above process requires just a small amount of ETH for the transaction fee (gas), so make sure your MyEtherWallet has enough ETH in balance. 233 | - Do not change or modify the above ABI/JSON Interface text. 234 | 235 | 236 | ### How to check if your address is correctly mapped and submitted 237 | 238 | 1. Go to the [CHX swap contract page on etherscan.io](https://etherscan.io/address/0x59a8d0bdf9e024f060b230f8f54f291f4d03e2d5#readContract) ("Read Contract" section/tab). 239 | 2. Enter your Ethereum address in the input box under "mappedAddresses" section. 240 | 3. Click the "Query" button. 241 | 4. Your native CHX address should appear under the button, with the contract submission confirmed and stated. 242 | 243 | If you made a mistake and mapped the wrong native CHX address, you will have to contact us to remove your mapping and allow you to do it again, **unless your tokens are already allocated on the Own blockchain**. 244 | 245 | 246 | ## Allocation of native CHX 247 | 248 | The allocation of the native CHX tokens on the Own blockchain will be performed by Own staff during the first week of June, and then periodically once every month. Please note that the token swap will be discontinued on 30/6/2021. 249 | 250 | The tokens will be allocated to individual CHX addresses on the Own public blockchain from our genesis address [`CHXGEzcvxLFDTnS4L5pETLXxLsp3heH6KK1`](https://wallet.weown.com/address/CHXGEzcvxLFDTnS4L5pETLXxLsp3heH6KK1). 251 | The balance on the genesis address represents all unallocated tokens, which are not yet swapped. 252 | 253 | You can always check the balance of your native CHX address by entering it in the ["Address info" page in Own wallet](https://wallet.weown.com/address) and clicking the "Get address" button. 254 | 255 | If your tokens are not allocated on the Own blockchain after one month, please contact our support team at support@weown.com. 256 | -------------------------------------------------------------------------------- /docs/CHXFunctionalSpecification.md: -------------------------------------------------------------------------------- 1 | Functional Specification of CHX Contracts 2 | ========================================= 3 | 4 | 5 | ## CHXToken Contract 6 | 7 | ![CHX Token Contract Hierarchy](CHXTokenContractHierarchy.png) 8 | 9 | `CHXToken` is an [ERC20](https://theethereum.wiki/w/index.php/ERC20_Token_Standard) token contract, with the main purpose of keeping the records of CHX token holders' balances, and enabling transfers of tokens. 10 | 11 | `CHXToken` contract is implemented by inheriting from base contracts available in [OpenZeppelin](https://github.com/OpenZeppelin/zeppelin-solidity) library, as well as extending them by adding new functionality: 12 | 13 | - Batch transfers 14 | - Transfer restriction 15 | - Draining stray Ether and other ERC20 tokens 16 | 17 | 18 | ### Batch Transfers 19 | 20 | To enable cost-effective transfers of tokens from/to multiple addresses, without having to process multiple transactions, this contract implements following batch transfer functions: 21 | 22 | - `batchTransfer` 23 | - `batchTransferFrom` 24 | - `batchTransferFromMany` 25 | - `batchTransferFromManyToMany` 26 | - `batchApprove` 27 | - `batchIncreaseApproval` 28 | - `batchDecreaseApproval` 29 | 30 | These functions are iterating over passed arrays of addresses/values and invoking equivalent standard ERC20 non-batch function in each iteration. 31 | 32 | 33 | ### Transfer Restriction 34 | 35 | Initial CHX token distribution will be done through token sale process, which requires transfers to be disabled for general public until the tokens sale is complete. However, contract owner must still be able to transfer tokens to investors during the token sale. To achieve this, `CHXToken` implements following: 36 | 37 | - `isRestricted` state variable. 38 | - `setRestrictedState` function, used to set value of `isRestricted` to `true` or `false`. 39 | - `tokenSaleContractAddress` variable. 40 | - `setTokenSaleContractAddress` function, used to set value of `tokenSaleContractAddress` variable to the address of the deployed token sale contract. 41 | - `restricted` function modifier, used to restrict function invocation to owner and tokenSaleContractAddress only, depending on the state of the `isRestricted` variable. This modifier is applied to all transfer related functions. 42 | 43 | 44 | ### Draining Stray Ether and Other ERC20 Tokens 45 | 46 | `CHXToken` contract is not supposed to ever receive any Ether or other tokens. To prevent mistakenly sent Ether or tokens from being locked in `CHXToken` forever, two functions are provided to enable token contract owner to drain Ether or other ERC20 compatible tokens from `CHXToken` address. 47 | 48 | 49 | ### Token Burning 50 | 51 | By inheriting from OpenZeppelin library's `BurnableToken` contract, `CHXToken` contract allows tokens to be burned. However, `CHXToken` contract allows this function to be executed only by contract owner. 52 | 53 | 54 | ## CHXTokenSale Contract 55 | 56 | `CHXTokenSale` contract is the contract used for initial CHX token distribution, which will happen during public token sale period. Token sale is the process where Ether is sent to the token sale contract's address, and CHX tokens are received immediately upon processing the transaction. 57 | 58 | `CHXTokenSale` contract inherits from `Whitelistable` contract, which implements the whitelist functionality, used to enforce *Know Your Customer* process (KYC). 59 | 60 | 61 | ### Configuring the Token Sale Settings 62 | 63 | `CHXTokenSale` contract has following configurable properties: 64 | 65 | - `saleStartTime` / `saleEndTime` which can be set by invoking `setSaleTime` function. 66 | - `maxGasPrice`, which can be set by invoking `setMaxGasPrice` function. 67 | - `minContribution`, which can be set by invoking `setMinContribution` function. 68 | - `maxContributionPhase1`, which can be set by invoking `setMaxContributionPhase1` function. 69 | - `maxContributionPhase2`, which can be set by invoking `setMaxContributionPhase2` function. 70 | - `phase1DurationInHours`, which can be set by invoking `setPhase1DurationInHours` function. 71 | 72 | Values passed into these functions should be as follows: 73 | 74 | - Time should be expressed in UNIX timestamp. 75 | - Gas price and contributions should be expressed in `wei`. 76 | - Duration of the phase 1 should be expressed in hours (conversion to seconds is done internally). 77 | 78 | 79 | ### Whitelist 80 | 81 | By inheriting from `Whitelistable`, `CHXTokenSale` contract has `addToWhitelist` and `removeFromWhitelist` functions, which can be used to add/remove investor addresses to/from the whitelist. Whitelist functionality is not available to anyone else, except contract owner and whitelist admin (as defined in `onlyOwnerOrWhitelistAdmin` modifier). Whitelist admin's address is stored in the `whitelistAdmin` variable, which can be set using the `setWhitelistAdmin` function. 82 | 83 | 84 | ### Sale 85 | 86 | Before the public sale starts, an amount of tokens will be allocated for sale by transferring them to `CHXTokenSale` contract address. This will enable the token sale contract to allocate tokens to contributors, but only up to the amount allocated for sale. 87 | 88 | During public sale, Ether is sent to token sale contract's address, resulting in invocation of the [fallback function](http://solidity.readthedocs.io/en/develop/contracts.html#fallback-function), which handles the purchase by doing following: 89 | 90 | - Ensure that contribution is sent during the time window between `saleStartTime` and `saleEndTime` variables. 91 | - Ensure that gas price is not higher than defined limit in `maxGasPrice` variable, to prevent unfair competition. 92 | - Ensure contributor's address is white-listed. 93 | - Ensure minimum and maximum contribution thresholds are met, where *maximum contribution* is defined as total amount of Ether sent from the same address. 94 | - Transfer tokens to contributor's address. 95 | 96 | 97 | ### Withdrawals 98 | 99 | Collected Ether in the token sale contract can be sent to an address of choice using `sendCollectedEther` function. This can be done multiple times during the sale. 100 | 101 | Remaining unsold tokens can be sent to an address of choice (usually back to the `CHXToken` contract owner address) using `sendRemainingTokens` function. Obviously, after remaining tokens are sent, tokens sale contract can't accept any more contributions because its token balance is zero (nothing left to sell). 102 | 103 | 104 | ## CHXVestingVault Contract 105 | 106 | The purpose of the `CHXVestingVault` contract is to provide the facility to lock the tokens until the specified moment in time, after which they can be withdrawn. Vesting vault has three properties: 107 | 108 | - `tokenContract` (reference to the `CHXToken` contract, used to invoke the transfer of held tokens to the beneficiary) 109 | - `beneficiary` (address of the account which will receive tokens once vesting time is reached and `withdrawTokens` function is invoked) 110 | - `vestingTime` (a **point in time** (not duration), after which tokens become available for withdrawal) 111 | 112 | `CHXVestingVault` contract might be created in many instances - whenever there is a need to have a certain amount of tokens locked until a certain point in time in the future. 113 | To avoid having to deploy and wire it up with `CHXToken` contract every time it's needed, we have created the `CHXVestingVaultFactory` contract, which has the `createCHXVestingVault` function as a convenient way to create a new vesting vault by specifying only beneficiary address and vesting time. 114 | Once a vesting vault is created, tokens can be transferred to its address. 115 | 116 | Tokens held in the `CHXVestingVault` can be withdrawn by invoking `withdrawTokens`, but only after `vestingTime` is reached. 117 | 118 | In the case that beneficiary's address is compromised, or he wants to give the right to withdraw tokens to someone else, he can change the beneficiary address by invoking the `changeBeneficiary` function. 119 | 120 | 121 | ## Deploying and Wiring Up 122 | 123 | After `CHXToken`, `CHXTokenSale` and `CHXVestingVaultFactory` contracts are independently deployed (to avoid hitting max gas limit), they should be wired up in following way: 124 | 125 | - `CHXToken` contract has a property named `tokenSaleContractAddress`, which should be set by calling the function `setTokenSaleContractAddress` and passing in the address of `CHXTokenSale` contract. 126 | - `CHXTokenSale` contract has a property named `tokenContract`, which should be set by calling the function `setTokenContract` and passing in the address of `CHXToken` contract. 127 | - `CHXVestingVaultFactory` contract has a property named `tokenContract`, which should be set by calling the function `setTokenContract` and passing in the address of `CHXToken` contract. 128 | 129 | 130 | ## CHXSwap Contract 131 | 132 | The purpose of the `CHXSwap` contract is to map Ethereum addresses to native CHX addresses on Own blockchain. This will enable holders of ERC20 CHX tokens to register an address on Own blockchain, to which the balance of CHX will be allocated. The swap process is explained in the [announcement article](https://medium.com/ownmarket/own-native-blockchain-token-swap-explained-faq-f725a5e0f4e9). 133 | 134 | 135 | ### Address Submission/Mapping 136 | 137 | `CHXSwap` contract exposes `mapAddress` function, which enables submission of a native CHX address (generated using [Own blockchain wallet](https://wallet.weown.com/wallet)), to be assigned to the sender's Ethereum address. 138 | 139 | 140 | ### Handling Mistakes 141 | 142 | One Ethereum address can submit a corresponding native CHX address only once. If a mistake has been made, in order to repeat the submission, Ethereum address owner will have to contact Own to remove the mapped address. Unless native CHX tokens are already allocated to the mapped address on Own blockchain, Own staff will remove the mapped address using `removeMappedAddress` function, which can be executed only by contract owner. 143 | 144 | To avoid abuse, address owner will have to prove the ownership of the Ethereum address by sending a small amount of ETH (just to cover reset TX fee) to Own's contract admin address. 145 | 146 | 147 | ### Draining Stray Ether and Other ERC20 Tokens 148 | 149 | `CHXSwap` contract is not supposed to ever receive any Ether or other ERC20 tokens. To prevent mistakenly sent Ether or ERC20 tokens from being locked in `CHXSwap` forever, two functions are provided to enable token contract owner to drain Ether or other ERC20 compatible tokens from `CHXSwap` address. 150 | -------------------------------------------------------------------------------- /src/test/CHXToken.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js') 2 | const e18 = helpers.e18 3 | 4 | const CHXToken = artifacts.require('./CHXToken.sol') 5 | 6 | contract('CHXToken', accounts => { 7 | const admin = accounts[0] 8 | const investor1 = accounts[1] 9 | const investor2 = accounts[2] 10 | const investor3 = accounts[3] 11 | const investor4 = accounts[4] 12 | 13 | let chxToken 14 | 15 | beforeEach(async () => { 16 | chxToken = chxToken || await CHXToken.deployed() 17 | }) 18 | 19 | it('initializes correctly', async () => { 20 | const totalSupply = e18(200000000) 21 | assert((await chxToken.totalSupply()).equals(totalSupply), 'Total supply mismatch') 22 | assert((await chxToken.balanceOf(admin)).equals(totalSupply), 'Admin balance mismatch') 23 | assert(await chxToken.isRestricted(), 'Should be restricted initially') 24 | }) 25 | 26 | it('rejects transfers if restricted', async () => { 27 | await helpers.shouldFail(chxToken.transfer(investor2, e18(1), {from: investor1})) 28 | }) 29 | 30 | it('allows transfers for owner even if restricted', async () => { 31 | // ARRANGE 32 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 33 | const tokenQty = e18(2000) 34 | 35 | // ACT 36 | await chxToken.transfer(investor1, tokenQty) 37 | 38 | // ASSERT 39 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 40 | assert(investor1BalanceBefore.add(tokenQty).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 41 | }) 42 | 43 | it('allows transfers for anyone when not restricted', async () => { 44 | // ARRANGE 45 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 46 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 47 | const tokenQty = e18(500) 48 | 49 | await chxToken.setRestrictedState(false) 50 | assert.equal(await chxToken.isRestricted(), false, 'Should not be restricted') 51 | 52 | // ACT 53 | await chxToken.transfer(investor2, tokenQty, {from: investor1}) 54 | 55 | // ASSERT 56 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 57 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 58 | assert(investor1BalanceBefore.sub(tokenQty).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 59 | assert(investor2BalanceBefore.add(tokenQty).equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 60 | }) 61 | 62 | it('supports batchTransfer', async () => { 63 | // ARRANGE 64 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 65 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 66 | const investor3BalanceBefore = await chxToken.balanceOf(investor3) 67 | const investor4BalanceBefore = await chxToken.balanceOf(investor4) 68 | 69 | // ACT 70 | await chxToken.batchTransfer( 71 | [investor2, investor3, investor4], 72 | [e18(200), e18(150), e18(100)], 73 | {from: investor1} 74 | ) 75 | 76 | // ASSERT 77 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 78 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 79 | const investor3BalanceAfter = await chxToken.balanceOf(investor3) 80 | const investor4BalanceAfter = await chxToken.balanceOf(investor4) 81 | 82 | assert(investor1BalanceBefore.sub(e18(450)).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 83 | assert(investor2BalanceBefore.add(e18(200)).equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 84 | assert(investor3BalanceBefore.add(e18(150)).equals(investor3BalanceAfter), 'Investor 3 balance mismatch') 85 | assert(investor4BalanceBefore.add(e18(100)).equals(investor4BalanceAfter), 'Investor 4 balance mismatch') 86 | }) 87 | 88 | it('supports batchApprove', async () => { 89 | // ARRANGE 90 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 91 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 92 | const investor3BalanceBefore = await chxToken.balanceOf(investor3) 93 | const investor4BalanceBefore = await chxToken.balanceOf(investor4) 94 | 95 | // ACT 96 | await chxToken.batchApprove( 97 | [investor2, investor3, investor4], 98 | [e18(70), e18(50), e18(40)], 99 | {from: investor1} 100 | ) 101 | 102 | await chxToken.transferFrom(investor1, investor2, e18(70), {from: investor2}) 103 | await chxToken.transferFrom(investor1, investor3, e18(50), {from: investor3}) 104 | await chxToken.transferFrom(investor1, investor4, e18(40), {from: investor4}) 105 | 106 | // ASSERT 107 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 108 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 109 | const investor3BalanceAfter = await chxToken.balanceOf(investor3) 110 | const investor4BalanceAfter = await chxToken.balanceOf(investor4) 111 | 112 | assert(investor1BalanceBefore.sub(e18(160)).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 113 | assert(investor2BalanceBefore.add(e18(70)).equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 114 | assert(investor3BalanceBefore.add(e18(50)).equals(investor3BalanceAfter), 'Investor 3 balance mismatch') 115 | assert(investor4BalanceBefore.add(e18(40)).equals(investor4BalanceAfter), 'Investor 4 balance mismatch') 116 | }) 117 | 118 | it('supports batchTransferFrom', async () => { 119 | // ARRANGE 120 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 121 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 122 | const investor3BalanceBefore = await chxToken.balanceOf(investor3) 123 | const investor4BalanceBefore = await chxToken.balanceOf(investor4) 124 | 125 | await chxToken.approve(investor2, e18(30), {from: investor1}) 126 | 127 | // ACT 128 | await chxToken.batchTransferFrom( 129 | investor1, 130 | [investor3, investor4], 131 | [e18(10), e18(20)], 132 | {from: investor2} 133 | ) 134 | 135 | // ASSERT 136 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 137 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 138 | const investor3BalanceAfter = await chxToken.balanceOf(investor3) 139 | const investor4BalanceAfter = await chxToken.balanceOf(investor4) 140 | 141 | assert(investor1BalanceBefore.sub(e18(30)).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 142 | assert(investor2BalanceBefore.equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 143 | assert(investor3BalanceBefore.add(e18(10)).equals(investor3BalanceAfter), 'Investor 3 balance mismatch') 144 | assert(investor4BalanceBefore.add(e18(20)).equals(investor4BalanceAfter), 'Investor 4 balance mismatch') 145 | }) 146 | 147 | it('supports batchTransferFromMany', async () => { 148 | // ARRANGE 149 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 150 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 151 | const investor3BalanceBefore = await chxToken.balanceOf(investor3) 152 | const investor4BalanceBefore = await chxToken.balanceOf(investor4) 153 | 154 | await chxToken.approve(investor1, e18(15), {from: investor2}) 155 | await chxToken.approve(investor1, e18(25), {from: investor3}) 156 | 157 | // ACT 158 | await chxToken.batchTransferFromMany( 159 | [investor2, investor3], 160 | investor4, 161 | [e18(15), e18(25)], 162 | {from: investor1} 163 | ) 164 | 165 | // ASSERT 166 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 167 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 168 | const investor3BalanceAfter = await chxToken.balanceOf(investor3) 169 | const investor4BalanceAfter = await chxToken.balanceOf(investor4) 170 | 171 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 172 | assert(investor2BalanceBefore.sub(e18(15)).equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 173 | assert(investor3BalanceBefore.sub(e18(25)).equals(investor3BalanceAfter), 'Investor 3 balance mismatch') 174 | assert(investor4BalanceBefore.add(e18(40)).equals(investor4BalanceAfter), 'Investor 4 balance mismatch') 175 | }) 176 | 177 | it('supports batchTransferFromManyToMany', async () => { 178 | // ARRANGE 179 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 180 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 181 | const investor3BalanceBefore = await chxToken.balanceOf(investor3) 182 | const investor4BalanceBefore = await chxToken.balanceOf(investor4) 183 | 184 | await chxToken.approve(admin, e18(50), {from: investor2}) 185 | await chxToken.approve(admin, e18(40), {from: investor4}) 186 | 187 | // ACT 188 | await chxToken.batchTransferFromManyToMany( 189 | [investor2, investor4], 190 | [investor1, investor3], 191 | [e18(50), e18(40)], 192 | {from: admin} 193 | ) 194 | 195 | // ASSERT 196 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 197 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 198 | const investor3BalanceAfter = await chxToken.balanceOf(investor3) 199 | const investor4BalanceAfter = await chxToken.balanceOf(investor4) 200 | 201 | assert(investor1BalanceBefore.add(e18(50)).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 202 | assert(investor2BalanceBefore.sub(e18(50)).equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 203 | assert(investor3BalanceBefore.add(e18(40)).equals(investor3BalanceAfter), 'Investor 3 balance mismatch') 204 | assert(investor4BalanceBefore.sub(e18(40)).equals(investor4BalanceAfter), 'Investor 4 balance mismatch') 205 | }) 206 | 207 | it('reduces total supply by burning tokens', async () => { 208 | // ARRANGE 209 | const totalSupplyBefore = await chxToken.totalSupply() 210 | const adminBalanceBefore = await chxToken.balanceOf(admin) 211 | const burnQty = e18(1000) 212 | 213 | // ACT 214 | await chxToken.burn(burnQty) 215 | 216 | // ASSERT 217 | const totalSupplyAfter = await chxToken.totalSupply() 218 | const adminBalanceAfter = await chxToken.balanceOf(admin) 219 | assert(totalSupplyBefore.sub(burnQty).equals(totalSupplyAfter), 'Total supply mismatch') 220 | assert(adminBalanceBefore.sub(burnQty).equals(adminBalanceAfter), 'Admin balance mismatch') 221 | }) 222 | }) 223 | -------------------------------------------------------------------------------- /src/test/CHXTokenSale.js: -------------------------------------------------------------------------------- 1 | const helpers = require('./helpers.js') 2 | const e18 = helpers.e18 3 | 4 | const CHXToken = artifacts.require('./CHXToken.sol') 5 | const CHXTokenSale = artifacts.require('./CHXTokenSale.sol') 6 | 7 | contract('CHXTokenSale', accounts => { 8 | const owner = accounts[0] 9 | const investor1 = accounts[1] 10 | const investor2 = accounts[2] 11 | const collectedEtherWallet = accounts[8] 12 | const whitelistAdmin = accounts[9] 13 | 14 | const calculateTokens = contribution => { 15 | const tokenPrice = web3.toWei(170, "szabo") 16 | const multiplier = e18(1) 17 | return web3.toBigNumber( 18 | web3.toBigNumber(contribution).mul(multiplier).div(tokenPrice).toFixed(0, 1) // ROUND_DOWN 19 | ) 20 | } 21 | 22 | const gasPrice = web3.toBigNumber(15e9) // 15 GWEI 23 | const tokensForSale = e18(100000) 24 | 25 | let chxToken 26 | let chxTokenSale 27 | 28 | beforeEach(async () => { 29 | chxToken = chxToken || await CHXToken.deployed() 30 | chxTokenSale = chxTokenSale || await CHXTokenSale.deployed() 31 | }) 32 | 33 | it('initializes correctly', async () => { 34 | const maxGasPrice = web3.toBigNumber(20e9) // 20 GWEI - to prevent "gas race" 35 | const minContribution = web3.toWei(100, 'finney') // 0.1 ETH 36 | const maxContributionPhase1 = web3.toWei(500, 'finney') // 0.5 ETH 37 | const maxContributionPhase2 = web3.toWei(10, 'ether') 38 | const phase1DurationInHours = web3.toBigNumber(24) 39 | 40 | await chxTokenSale.setTokenContract(chxToken.address) 41 | await chxToken.setTokenSaleContractAddress(chxTokenSale.address) 42 | await chxToken.transfer(chxTokenSale.address, tokensForSale) 43 | 44 | assert.equal(await chxToken.tokenSaleContractAddress(), chxTokenSale.address, 45 | 'CHXToken.tokenSaleContractAddress mismatch') 46 | assert.equal(await chxTokenSale.tokenContract(), chxToken.address, 47 | 'CHXTokenSale.tokenContract mismatch') 48 | assert((await chxToken.balanceOf(chxTokenSale.address)).equals(tokensForSale), 49 | 'Tokens for sale mismatch') 50 | assert((await chxTokenSale.maxGasPrice()).equals(maxGasPrice), 51 | 'maxGasPrice mismatch') 52 | assert((await chxTokenSale.minContribution()).equals(minContribution), 53 | 'minContribution mismatch') 54 | assert((await chxTokenSale.maxContributionPhase1()).equals(maxContributionPhase1), 55 | 'maxContributionPhase1 mismatch') 56 | assert((await chxTokenSale.maxContributionPhase2()).equals(maxContributionPhase2), 57 | 'maxContributionPhase2 mismatch') 58 | assert((await chxTokenSale.phase1DurationInHours()).equals(phase1DurationInHours), 59 | 'phase1DurationInHours mismatch') 60 | }) 61 | 62 | it('rejects contributions before saleStartTime', async () => { 63 | // ARRANGE 64 | const contribution = web3.toWei(500, "finney") 65 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 66 | const evmTime = web3.toBigNumber(helpers.lastEVMTime()) 67 | await chxTokenSale.setSaleTime( 68 | evmTime.add(helpers.duration.hours(1)), 69 | evmTime.add(helpers.duration.hours(2)), 70 | {from: owner}) 71 | 72 | // ACT 73 | await helpers.shouldFail( 74 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice})) 75 | 76 | // ASSERT 77 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 78 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 79 | }) 80 | 81 | it('rejects contributions after saleEndTime', async () => { 82 | // ARRANGE 83 | const contribution = web3.toWei(500, "finney") 84 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 85 | const evmTime = web3.toBigNumber(helpers.lastEVMTime()) 86 | await chxTokenSale.setSaleTime( 87 | evmTime.sub(helpers.duration.hours(2)), 88 | evmTime.sub(helpers.duration.hours(1)), 89 | {from: owner}) 90 | 91 | // ACT 92 | await helpers.shouldFail( 93 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice})) 94 | 95 | // ASSERT 96 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 97 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 98 | }) 99 | 100 | it('accepts contributions between saleStartTime and saleEndTime', async () => { 101 | // ARRANGE 102 | const contribution = web3.toWei(500, "finney") 103 | const tokens = calculateTokens(contribution) 104 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 105 | const evmTime = web3.toBigNumber(helpers.lastEVMTime()) 106 | await chxTokenSale.setSaleTime( 107 | evmTime.sub(helpers.duration.hours(2)), 108 | evmTime.add(helpers.duration.hours(2)), 109 | {from: owner}) 110 | await chxTokenSale.setPhase1DurationInHours(3, {from: owner}) // In phase 1 111 | await chxTokenSale.addToWhitelist([investor1], {from: owner}) 112 | 113 | // ACT 114 | await chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice}) 115 | 116 | // ASSERT 117 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 118 | assert(investor1BalanceBefore.add(tokens).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 119 | }) 120 | 121 | it('rejects contributions with gas price over the limit', async () => { 122 | // ARRANGE 123 | const contribution = web3.toWei(500, "finney") 124 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 125 | const highGasPrice = web3.toBigNumber(50e9) // 50 GWEI 126 | 127 | // ACT 128 | await helpers.shouldFail( 129 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: highGasPrice})) 130 | 131 | // ASSERT 132 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 133 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 134 | }) 135 | 136 | it('rejects contributions if address not whitelisted', async () => { 137 | // ARRANGE 138 | const contribution = web3.toWei(500, "finney") 139 | const investor2BalanceBefore = await chxToken.balanceOf(investor2) 140 | 141 | // ACT 142 | await helpers.shouldFail( 143 | chxTokenSale.sendTransaction({from: investor2, value: contribution, gasPrice: gasPrice})) 144 | 145 | // ASSERT 146 | const investor2BalanceAfter = await chxToken.balanceOf(investor2) 147 | assert(investor2BalanceBefore.equals(investor2BalanceAfter), 'Investor 2 balance mismatch') 148 | }) 149 | 150 | it('rejects too low contributions', async () => { 151 | // ARRANGE 152 | const contribution = web3.toWei(90, "finney") // 0.09 ETH 153 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 154 | 155 | // ACT 156 | await helpers.shouldFail( 157 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice})) 158 | 159 | // ASSERT 160 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 161 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 162 | }) 163 | 164 | it('rejects too high contributions in phase 1', async () => { 165 | // ARRANGE 166 | const contribution = web3.toWei(1, "wei") // Total contribution counts 167 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 168 | 169 | // ACT 170 | await helpers.shouldFail( 171 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice})) 172 | 173 | // ASSERT 174 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 175 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 176 | }) 177 | 178 | it('accepts contributions up to the phase 2 limit', async () => { 179 | // ARRANGE 180 | const contribution = web3.toWei(9500, "finney") // 0.5 ETH invested in phase 1 181 | const tokens = calculateTokens(contribution) 182 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 183 | await chxTokenSale.setPhase1DurationInHours(1, {from: owner}) // In phase 2 184 | 185 | // ACT 186 | await chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice}) 187 | 188 | // ASSERT 189 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 190 | assert(investor1BalanceBefore.add(tokens).equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 191 | }) 192 | 193 | it('rejects too high contributions in phase 2', async () => { 194 | // ARRANGE 195 | const contribution = web3.toWei(1, "wei") // Total contribution counts 196 | const investor1BalanceBefore = await chxToken.balanceOf(investor1) 197 | 198 | // ACT 199 | await helpers.shouldFail( 200 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice})) 201 | 202 | // ASSERT 203 | const investor1BalanceAfter = await chxToken.balanceOf(investor1) 204 | assert(investor1BalanceBefore.equals(investor1BalanceAfter), 'Investor 1 balance mismatch') 205 | }) 206 | 207 | it('rejects contributions over total number of available tokens for sale', async () => { 208 | // ARRANGE 209 | await chxTokenSale.setMaxContributionPhase2(web3.toWei(100, "ether")) 210 | const investor1TokenBalanceBefore = await chxToken.balanceOf(investor1) 211 | const contribution = web3.toWei(50, "ether") 212 | const investor1EtherBalance = web3.eth.getBalance(investor1) 213 | assert(investor1EtherBalance.gt(contribution), 'Not enough ETH for test') 214 | assert(calculateTokens(contribution).gt(tokensForSale), 'Too low contribution for test') 215 | 216 | // ACT 217 | await helpers.shouldFail( 218 | chxTokenSale.sendTransaction({from: investor1, value: contribution, gasPrice: gasPrice})) 219 | 220 | // ASSERT 221 | const investor1TokenBalanceAfter = await chxToken.balanceOf(investor1) 222 | assert(investor1TokenBalanceBefore.equals(investor1TokenBalanceAfter), 'Investor 1 balance mismatch') 223 | }) 224 | 225 | it('maintains sum of ETH contributions per investor and total collected ETH', async () => { 226 | // ARRANGE 227 | const contribution1 = web3.toWei(10, "ether") // Investor 1 228 | const contribution2 = web3.toWei(1, "ether") // Investor 2 229 | await chxTokenSale.addToWhitelist([investor2], {from: owner}) 230 | 231 | // ACT 232 | await chxTokenSale.sendTransaction({from: investor2, value: contribution2, gasPrice: gasPrice}) 233 | 234 | // ASSERT 235 | const etherContributions1 = await chxTokenSale.etherContributions(investor1) 236 | const etherContributions2 = await chxTokenSale.etherContributions(investor2) 237 | const etherCollected = await chxTokenSale.etherCollected() 238 | 239 | assert(etherContributions1.equals(contribution1), 'etherContributions1 mismatch') 240 | assert(etherContributions2.equals(contribution2), 'etherContributions2 mismatch') 241 | assert(etherContributions1.add(etherContributions2).equals(etherCollected), 'etherCollected mismatch') 242 | assert((web3.eth.getBalance(chxTokenSale.address)).equals(etherCollected), 'contract ETH balance mismatch') 243 | }) 244 | 245 | it('maintains sum of allocated tokens per investor and total tokens sold', async () => { 246 | // ARRANGE 247 | const investor1Balance = await chxToken.balanceOf(investor1) 248 | const investor2Balance = await chxToken.balanceOf(investor2) 249 | const tokenAllocations1 = await chxTokenSale.tokenAllocations(investor1) 250 | const tokenAllocations2 = await chxTokenSale.tokenAllocations(investor2) 251 | const tokensSold = await chxTokenSale.tokensSold() 252 | 253 | // ASSERT 254 | assert(tokenAllocations1.equals(investor1Balance), 'tokenAllocations1 mismatch') 255 | assert(tokenAllocations2.equals(investor2Balance), 'tokenAllocations2 mismatch') 256 | assert(tokenAllocations1.add(tokenAllocations2).equals(tokensSold), 'tokensSold mismatch') 257 | }) 258 | 259 | it('sends collected ether to provided address', async () => { 260 | // ARRANGE 261 | const etherContributions1 = await chxTokenSale.etherContributions(investor1) // Investor 1 262 | const etherContributions2 = await chxTokenSale.etherContributions(investor2) // Investor 2 263 | const totalContributions = etherContributions1.add(etherContributions2) 264 | const collectedEtherWalletBalanceBefore = web3.eth.getBalance(collectedEtherWallet) 265 | const chxTokenSaleBalanceBefore = web3.eth.getBalance(chxTokenSale.address) 266 | 267 | // ACT 268 | await chxTokenSale.sendCollectedEther(collectedEtherWallet, {from: owner}) 269 | 270 | // ASSERT 271 | const chxTokenSaleBalanceAfter = web3.eth.getBalance(chxTokenSale.address) 272 | const collectedEtherWalletBalanceAfter = web3.eth.getBalance(collectedEtherWallet) 273 | 274 | assert(chxTokenSaleBalanceAfter.equals(0), 'chxTokenSaleBalanceAfter should be zero') 275 | assert(chxTokenSaleBalanceBefore.gt(chxTokenSaleBalanceAfter), 276 | 'chxTokenSaleBalanceAfter should have decreased') 277 | assert(collectedEtherWalletBalanceBefore.add(totalContributions).equals(collectedEtherWalletBalanceAfter), 278 | 'collectedEtherWallet ETH balance mismatch') 279 | }) 280 | 281 | it('sends remaining unsold tokens to provided address', async () => { 282 | // ARRANGE 283 | const ownerTokenBalanceBefore = await chxToken.balanceOf(owner) 284 | const chxTokenSaleTokenBalanceBefore = await chxToken.balanceOf(chxTokenSale.address) 285 | const tokensSold = await chxTokenSale.tokensSold() 286 | const remainingTokens = tokensForSale.sub(tokensSold) 287 | assert(chxTokenSaleTokenBalanceBefore.equals(remainingTokens), 'Remaining tokens calculation mismatch') 288 | 289 | // ACT 290 | await chxTokenSale.sendRemainingTokens(owner, {from: owner}) 291 | 292 | // ASSERT 293 | const chxTokenSaleTokenBalanceAfter = await chxToken.balanceOf(chxTokenSale.address) 294 | const ownerTokenBalanceAfter = await chxToken.balanceOf(owner) 295 | 296 | assert(chxTokenSaleTokenBalanceAfter.equals(0), 'chxTokenSaleTokenBalanceAfter mismatch') 297 | assert(ownerTokenBalanceBefore.add(remainingTokens).equals(ownerTokenBalanceAfter), 298 | 'ownerTokenBalanceAfter mismatch') 299 | }) 300 | }) 301 | -------------------------------------------------------------------------------- /src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chainium-smart-contracts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-regex": { 8 | "version": "2.1.1", 9 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 10 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 11 | "dev": true 12 | }, 13 | "balanced-match": { 14 | "version": "1.0.0", 15 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 16 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 17 | "dev": true 18 | }, 19 | "brace-expansion": { 20 | "version": "1.1.11", 21 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 22 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 23 | "dev": true, 24 | "requires": { 25 | "balanced-match": "^1.0.0", 26 | "concat-map": "0.0.1" 27 | } 28 | }, 29 | "browser-stdout": { 30 | "version": "1.3.0", 31 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 32 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 33 | "dev": true 34 | }, 35 | "camelcase": { 36 | "version": "3.0.0", 37 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", 38 | "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", 39 | "dev": true 40 | }, 41 | "cliui": { 42 | "version": "3.2.0", 43 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", 44 | "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", 45 | "dev": true, 46 | "requires": { 47 | "string-width": "^1.0.1", 48 | "strip-ansi": "^3.0.1", 49 | "wrap-ansi": "^2.0.0" 50 | } 51 | }, 52 | "code-point-at": { 53 | "version": "1.1.0", 54 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 55 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", 56 | "dev": true 57 | }, 58 | "commander": { 59 | "version": "2.9.0", 60 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", 61 | "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", 62 | "dev": true, 63 | "requires": { 64 | "graceful-readlink": ">= 1.0.0" 65 | } 66 | }, 67 | "concat-map": { 68 | "version": "0.0.1", 69 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 70 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 71 | "dev": true 72 | }, 73 | "debug": { 74 | "version": "2.6.8", 75 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 76 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 77 | "dev": true, 78 | "requires": { 79 | "ms": "2.0.0" 80 | } 81 | }, 82 | "decamelize": { 83 | "version": "1.2.0", 84 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 85 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 86 | "dev": true 87 | }, 88 | "diff": { 89 | "version": "3.2.0", 90 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", 91 | "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", 92 | "dev": true 93 | }, 94 | "error-ex": { 95 | "version": "1.3.2", 96 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 97 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 98 | "dev": true, 99 | "requires": { 100 | "is-arrayish": "^0.2.1" 101 | } 102 | }, 103 | "escape-string-regexp": { 104 | "version": "1.0.5", 105 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 106 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 107 | "dev": true 108 | }, 109 | "find-up": { 110 | "version": "1.1.2", 111 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", 112 | "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", 113 | "dev": true, 114 | "requires": { 115 | "path-exists": "^2.0.0", 116 | "pinkie-promise": "^2.0.0" 117 | } 118 | }, 119 | "fs-extra": { 120 | "version": "0.30.0", 121 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", 122 | "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", 123 | "dev": true, 124 | "requires": { 125 | "graceful-fs": "^4.1.2", 126 | "jsonfile": "^2.1.0", 127 | "klaw": "^1.0.0", 128 | "path-is-absolute": "^1.0.0", 129 | "rimraf": "^2.2.8" 130 | } 131 | }, 132 | "fs.realpath": { 133 | "version": "1.0.0", 134 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 135 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 136 | "dev": true 137 | }, 138 | "get-caller-file": { 139 | "version": "1.0.3", 140 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", 141 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", 142 | "dev": true 143 | }, 144 | "glob": { 145 | "version": "7.1.1", 146 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", 147 | "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", 148 | "dev": true, 149 | "requires": { 150 | "fs.realpath": "^1.0.0", 151 | "inflight": "^1.0.4", 152 | "inherits": "2", 153 | "minimatch": "^3.0.2", 154 | "once": "^1.3.0", 155 | "path-is-absolute": "^1.0.0" 156 | } 157 | }, 158 | "graceful-fs": { 159 | "version": "4.1.15", 160 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 161 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", 162 | "dev": true 163 | }, 164 | "graceful-readlink": { 165 | "version": "1.0.1", 166 | "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", 167 | "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", 168 | "dev": true 169 | }, 170 | "growl": { 171 | "version": "1.9.2", 172 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", 173 | "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", 174 | "dev": true 175 | }, 176 | "has-flag": { 177 | "version": "1.0.0", 178 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", 179 | "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", 180 | "dev": true 181 | }, 182 | "he": { 183 | "version": "1.1.1", 184 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 185 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 186 | "dev": true 187 | }, 188 | "hosted-git-info": { 189 | "version": "2.7.1", 190 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", 191 | "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", 192 | "dev": true 193 | }, 194 | "inflight": { 195 | "version": "1.0.6", 196 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 197 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 198 | "dev": true, 199 | "requires": { 200 | "once": "^1.3.0", 201 | "wrappy": "1" 202 | } 203 | }, 204 | "inherits": { 205 | "version": "2.0.3", 206 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 207 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 208 | "dev": true 209 | }, 210 | "invert-kv": { 211 | "version": "1.0.0", 212 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", 213 | "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", 214 | "dev": true 215 | }, 216 | "is-arrayish": { 217 | "version": "0.2.1", 218 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 219 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 220 | "dev": true 221 | }, 222 | "is-fullwidth-code-point": { 223 | "version": "1.0.0", 224 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 225 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 226 | "dev": true, 227 | "requires": { 228 | "number-is-nan": "^1.0.0" 229 | } 230 | }, 231 | "is-utf8": { 232 | "version": "0.2.1", 233 | "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", 234 | "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", 235 | "dev": true 236 | }, 237 | "json3": { 238 | "version": "3.3.2", 239 | "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", 240 | "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", 241 | "dev": true 242 | }, 243 | "jsonfile": { 244 | "version": "2.4.0", 245 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", 246 | "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", 247 | "dev": true, 248 | "requires": { 249 | "graceful-fs": "^4.1.6" 250 | } 251 | }, 252 | "klaw": { 253 | "version": "1.3.1", 254 | "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", 255 | "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", 256 | "dev": true, 257 | "requires": { 258 | "graceful-fs": "^4.1.9" 259 | } 260 | }, 261 | "lcid": { 262 | "version": "1.0.0", 263 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", 264 | "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", 265 | "dev": true, 266 | "requires": { 267 | "invert-kv": "^1.0.0" 268 | } 269 | }, 270 | "load-json-file": { 271 | "version": "1.1.0", 272 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", 273 | "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", 274 | "dev": true, 275 | "requires": { 276 | "graceful-fs": "^4.1.2", 277 | "parse-json": "^2.2.0", 278 | "pify": "^2.0.0", 279 | "pinkie-promise": "^2.0.0", 280 | "strip-bom": "^2.0.0" 281 | } 282 | }, 283 | "lodash._baseassign": { 284 | "version": "3.2.0", 285 | "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", 286 | "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", 287 | "dev": true, 288 | "requires": { 289 | "lodash._basecopy": "^3.0.0", 290 | "lodash.keys": "^3.0.0" 291 | } 292 | }, 293 | "lodash._basecopy": { 294 | "version": "3.0.1", 295 | "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", 296 | "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", 297 | "dev": true 298 | }, 299 | "lodash._basecreate": { 300 | "version": "3.0.3", 301 | "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", 302 | "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", 303 | "dev": true 304 | }, 305 | "lodash._getnative": { 306 | "version": "3.9.1", 307 | "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", 308 | "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", 309 | "dev": true 310 | }, 311 | "lodash._isiterateecall": { 312 | "version": "3.0.9", 313 | "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", 314 | "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", 315 | "dev": true 316 | }, 317 | "lodash.assign": { 318 | "version": "4.2.0", 319 | "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", 320 | "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", 321 | "dev": true 322 | }, 323 | "lodash.create": { 324 | "version": "3.1.1", 325 | "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", 326 | "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", 327 | "dev": true, 328 | "requires": { 329 | "lodash._baseassign": "^3.0.0", 330 | "lodash._basecreate": "^3.0.0", 331 | "lodash._isiterateecall": "^3.0.0" 332 | } 333 | }, 334 | "lodash.isarguments": { 335 | "version": "3.1.0", 336 | "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", 337 | "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", 338 | "dev": true 339 | }, 340 | "lodash.isarray": { 341 | "version": "3.0.4", 342 | "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", 343 | "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", 344 | "dev": true 345 | }, 346 | "lodash.keys": { 347 | "version": "3.1.2", 348 | "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", 349 | "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", 350 | "dev": true, 351 | "requires": { 352 | "lodash._getnative": "^3.0.0", 353 | "lodash.isarguments": "^3.0.0", 354 | "lodash.isarray": "^3.0.0" 355 | } 356 | }, 357 | "memorystream": { 358 | "version": "0.3.1", 359 | "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", 360 | "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", 361 | "dev": true 362 | }, 363 | "minimatch": { 364 | "version": "3.0.4", 365 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 366 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 367 | "dev": true, 368 | "requires": { 369 | "brace-expansion": "^1.1.7" 370 | } 371 | }, 372 | "minimist": { 373 | "version": "0.0.8", 374 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 375 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 376 | "dev": true 377 | }, 378 | "mkdirp": { 379 | "version": "0.5.1", 380 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 381 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 382 | "dev": true, 383 | "requires": { 384 | "minimist": "0.0.8" 385 | } 386 | }, 387 | "mocha": { 388 | "version": "3.5.3", 389 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", 390 | "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", 391 | "dev": true, 392 | "requires": { 393 | "browser-stdout": "1.3.0", 394 | "commander": "2.9.0", 395 | "debug": "2.6.8", 396 | "diff": "3.2.0", 397 | "escape-string-regexp": "1.0.5", 398 | "glob": "7.1.1", 399 | "growl": "1.9.2", 400 | "he": "1.1.1", 401 | "json3": "3.3.2", 402 | "lodash.create": "3.1.1", 403 | "mkdirp": "0.5.1", 404 | "supports-color": "3.1.2" 405 | } 406 | }, 407 | "ms": { 408 | "version": "2.0.0", 409 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 410 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 411 | "dev": true 412 | }, 413 | "normalize-package-data": { 414 | "version": "2.5.0", 415 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", 416 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", 417 | "dev": true, 418 | "requires": { 419 | "hosted-git-info": "^2.1.4", 420 | "resolve": "^1.10.0", 421 | "semver": "2 || 3 || 4 || 5", 422 | "validate-npm-package-license": "^3.0.1" 423 | } 424 | }, 425 | "number-is-nan": { 426 | "version": "1.0.1", 427 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 428 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", 429 | "dev": true 430 | }, 431 | "once": { 432 | "version": "1.4.0", 433 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 434 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 435 | "dev": true, 436 | "requires": { 437 | "wrappy": "1" 438 | } 439 | }, 440 | "original-require": { 441 | "version": "1.0.1", 442 | "resolved": "https://registry.npmjs.org/original-require/-/original-require-1.0.1.tgz", 443 | "integrity": "sha1-DxMEcVhM0zURxew4yNWSE/msXiA=", 444 | "dev": true 445 | }, 446 | "os-locale": { 447 | "version": "1.4.0", 448 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", 449 | "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", 450 | "dev": true, 451 | "requires": { 452 | "lcid": "^1.0.0" 453 | } 454 | }, 455 | "parse-json": { 456 | "version": "2.2.0", 457 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", 458 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", 459 | "dev": true, 460 | "requires": { 461 | "error-ex": "^1.2.0" 462 | } 463 | }, 464 | "path-exists": { 465 | "version": "2.1.0", 466 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", 467 | "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", 468 | "dev": true, 469 | "requires": { 470 | "pinkie-promise": "^2.0.0" 471 | } 472 | }, 473 | "path-is-absolute": { 474 | "version": "1.0.1", 475 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 476 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 477 | "dev": true 478 | }, 479 | "path-parse": { 480 | "version": "1.0.6", 481 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 482 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 483 | "dev": true 484 | }, 485 | "path-type": { 486 | "version": "1.1.0", 487 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", 488 | "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", 489 | "dev": true, 490 | "requires": { 491 | "graceful-fs": "^4.1.2", 492 | "pify": "^2.0.0", 493 | "pinkie-promise": "^2.0.0" 494 | } 495 | }, 496 | "pify": { 497 | "version": "2.3.0", 498 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 499 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 500 | "dev": true 501 | }, 502 | "pinkie": { 503 | "version": "2.0.4", 504 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 505 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 506 | "dev": true 507 | }, 508 | "pinkie-promise": { 509 | "version": "2.0.1", 510 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 511 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 512 | "dev": true, 513 | "requires": { 514 | "pinkie": "^2.0.0" 515 | } 516 | }, 517 | "read-pkg": { 518 | "version": "1.1.0", 519 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", 520 | "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", 521 | "dev": true, 522 | "requires": { 523 | "load-json-file": "^1.0.0", 524 | "normalize-package-data": "^2.3.2", 525 | "path-type": "^1.0.0" 526 | } 527 | }, 528 | "read-pkg-up": { 529 | "version": "1.0.1", 530 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", 531 | "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", 532 | "dev": true, 533 | "requires": { 534 | "find-up": "^1.0.0", 535 | "read-pkg": "^1.0.0" 536 | } 537 | }, 538 | "require-directory": { 539 | "version": "2.1.1", 540 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 541 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 542 | "dev": true 543 | }, 544 | "require-from-string": { 545 | "version": "1.2.1", 546 | "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", 547 | "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", 548 | "dev": true 549 | }, 550 | "require-main-filename": { 551 | "version": "1.0.1", 552 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 553 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", 554 | "dev": true 555 | }, 556 | "resolve": { 557 | "version": "1.11.0", 558 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", 559 | "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", 560 | "dev": true, 561 | "requires": { 562 | "path-parse": "^1.0.6" 563 | } 564 | }, 565 | "rimraf": { 566 | "version": "2.6.3", 567 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 568 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 569 | "dev": true, 570 | "requires": { 571 | "glob": "^7.1.3" 572 | }, 573 | "dependencies": { 574 | "glob": { 575 | "version": "7.1.4", 576 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", 577 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", 578 | "dev": true, 579 | "requires": { 580 | "fs.realpath": "^1.0.0", 581 | "inflight": "^1.0.4", 582 | "inherits": "2", 583 | "minimatch": "^3.0.4", 584 | "once": "^1.3.0", 585 | "path-is-absolute": "^1.0.0" 586 | } 587 | } 588 | } 589 | }, 590 | "semver": { 591 | "version": "5.7.0", 592 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 593 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", 594 | "dev": true 595 | }, 596 | "set-blocking": { 597 | "version": "2.0.0", 598 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 599 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 600 | "dev": true 601 | }, 602 | "solc": { 603 | "version": "0.4.19", 604 | "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.19.tgz", 605 | "integrity": "sha512-hvi/vi9rQcB73poRLoLRfQIYKwmdhrNbZlOOFCGd5v58gEsYEUr3+oHPSXhyk4CFNchWC2ojpMYrHDJNm0h4jQ==", 606 | "dev": true, 607 | "requires": { 608 | "fs-extra": "^0.30.0", 609 | "memorystream": "^0.3.1", 610 | "require-from-string": "^1.1.0", 611 | "semver": "^5.3.0", 612 | "yargs": "^4.7.1" 613 | } 614 | }, 615 | "spdx-correct": { 616 | "version": "3.1.0", 617 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", 618 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", 619 | "dev": true, 620 | "requires": { 621 | "spdx-expression-parse": "^3.0.0", 622 | "spdx-license-ids": "^3.0.0" 623 | } 624 | }, 625 | "spdx-exceptions": { 626 | "version": "2.2.0", 627 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", 628 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", 629 | "dev": true 630 | }, 631 | "spdx-expression-parse": { 632 | "version": "3.0.0", 633 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", 634 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", 635 | "dev": true, 636 | "requires": { 637 | "spdx-exceptions": "^2.1.0", 638 | "spdx-license-ids": "^3.0.0" 639 | } 640 | }, 641 | "spdx-license-ids": { 642 | "version": "3.0.4", 643 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", 644 | "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", 645 | "dev": true 646 | }, 647 | "string-width": { 648 | "version": "1.0.2", 649 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 650 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 651 | "dev": true, 652 | "requires": { 653 | "code-point-at": "^1.0.0", 654 | "is-fullwidth-code-point": "^1.0.0", 655 | "strip-ansi": "^3.0.0" 656 | } 657 | }, 658 | "strip-ansi": { 659 | "version": "3.0.1", 660 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 661 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 662 | "dev": true, 663 | "requires": { 664 | "ansi-regex": "^2.0.0" 665 | } 666 | }, 667 | "strip-bom": { 668 | "version": "2.0.0", 669 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", 670 | "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", 671 | "dev": true, 672 | "requires": { 673 | "is-utf8": "^0.2.0" 674 | } 675 | }, 676 | "supports-color": { 677 | "version": "3.1.2", 678 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", 679 | "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", 680 | "dev": true, 681 | "requires": { 682 | "has-flag": "^1.0.0" 683 | } 684 | }, 685 | "truffle": { 686 | "version": "4.1.0", 687 | "resolved": "https://registry.npmjs.org/truffle/-/truffle-4.1.0.tgz", 688 | "integrity": "sha512-6uArrTfq3Pe/pq2qjtsOUDtG4O4JEucnHjcHBPRYIY5Ra7bjFhYjqNFZ+66mZECLgLwLmrObviJRchXX0dkSSQ==", 689 | "dev": true, 690 | "requires": { 691 | "mocha": "^3.4.2", 692 | "original-require": "^1.0.1", 693 | "solc": "0.4.19" 694 | } 695 | }, 696 | "validate-npm-package-license": { 697 | "version": "3.0.4", 698 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", 699 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", 700 | "dev": true, 701 | "requires": { 702 | "spdx-correct": "^3.0.0", 703 | "spdx-expression-parse": "^3.0.0" 704 | } 705 | }, 706 | "which-module": { 707 | "version": "1.0.0", 708 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", 709 | "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", 710 | "dev": true 711 | }, 712 | "window-size": { 713 | "version": "0.2.0", 714 | "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", 715 | "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", 716 | "dev": true 717 | }, 718 | "wrap-ansi": { 719 | "version": "2.1.0", 720 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 721 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 722 | "dev": true, 723 | "requires": { 724 | "string-width": "^1.0.1", 725 | "strip-ansi": "^3.0.1" 726 | } 727 | }, 728 | "wrappy": { 729 | "version": "1.0.2", 730 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 731 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 732 | "dev": true 733 | }, 734 | "y18n": { 735 | "version": "3.2.1", 736 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", 737 | "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", 738 | "dev": true 739 | }, 740 | "yargs": { 741 | "version": "4.8.1", 742 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", 743 | "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", 744 | "dev": true, 745 | "requires": { 746 | "cliui": "^3.2.0", 747 | "decamelize": "^1.1.1", 748 | "get-caller-file": "^1.0.1", 749 | "lodash.assign": "^4.0.3", 750 | "os-locale": "^1.4.0", 751 | "read-pkg-up": "^1.0.1", 752 | "require-directory": "^2.1.1", 753 | "require-main-filename": "^1.0.1", 754 | "set-blocking": "^2.0.0", 755 | "string-width": "^1.0.1", 756 | "which-module": "^1.0.0", 757 | "window-size": "^0.2.0", 758 | "y18n": "^3.2.1", 759 | "yargs-parser": "^2.4.1" 760 | } 761 | }, 762 | "yargs-parser": { 763 | "version": "2.4.1", 764 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", 765 | "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", 766 | "dev": true, 767 | "requires": { 768 | "camelcase": "^3.0.0", 769 | "lodash.assign": "^4.0.6" 770 | } 771 | }, 772 | "zeppelin-solidity": { 773 | "version": "1.4.0", 774 | "resolved": "https://registry.npmjs.org/zeppelin-solidity/-/zeppelin-solidity-1.4.0.tgz", 775 | "integrity": "sha512-eJfD1Zf0ZP2m0oYnB/HM7JPJ81MNjs3qt+c/TRd5kvCSOU3EfYNHz5YfFgzkJzvPUGs0Zg8spzGROlqXShvisg==", 776 | "dev": true 777 | } 778 | } 779 | } 780 | --------------------------------------------------------------------------------