├── scripts ├── coverage.sh └── test.sh ├── test ├── helpers │ ├── EVMThrow.js │ ├── ether.js │ ├── gwei.js │ ├── assertJump.js │ ├── latestTime.js │ ├── toPromise.js │ ├── hashMessage.js │ ├── timer.js │ ├── advanceToBlock.js │ ├── expectThrow.js │ ├── transactionMined.js │ └── increaseTime.js └── Controller.js ├── contracts ├── Migrations.sol ├── Controlled.sol ├── ERC20.sol ├── iEthealSale.sol ├── EthealToken.sol ├── SafeMath.sol ├── HasNoTokens.sol ├── FinalizableCrowdsale.sol ├── Pausable.sol ├── Ownable.sol ├── ERC20MiniMe.sol ├── ECRecovery.sol ├── CappedCrowdsale.sol ├── RefundVault.sol ├── TokenController.sol ├── RefundableCrowdsale.sol ├── EthealWhitelist.sol ├── Crowdsale.sol ├── TokenVesting.sol ├── EthealPromoToken.sol ├── Hodler.sol ├── EthealDeposit.sol ├── EthealController.sol ├── AbstractVirtualToken.sol ├── Wallet.sol ├── EthealNormalSale.sol ├── EthealPreSale.sol └── MiniMeToken.sol ├── truffle-config.js ├── package.json ├── LICENSE ├── migrations └── 2_deploy_contracts.js └── README.md /scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SOLIDITY_COVERAGE=true scripts/test.sh 4 | -------------------------------------------------------------------------------- /test/helpers/EVMThrow.js: -------------------------------------------------------------------------------- 1 | export default 'VM Exception while processing transaction: revert' 2 | -------------------------------------------------------------------------------- /test/helpers/ether.js: -------------------------------------------------------------------------------- 1 | export default function ether(n) { 2 | return new web3.BigNumber(web3.toWei(n, 'ether')) 3 | } 4 | -------------------------------------------------------------------------------- /test/helpers/gwei.js: -------------------------------------------------------------------------------- 1 | export default function gwei(n) { 2 | return new web3.BigNumber(web3.toWei(n, 'gwei')) 3 | } 4 | -------------------------------------------------------------------------------- /test/helpers/assertJump.js: -------------------------------------------------------------------------------- 1 | module.exports = function(error) { 2 | assert.isAbove(error.message.search('invalid opcode'), -1, 'Invalid opcode error must be returned'); 3 | } 4 | -------------------------------------------------------------------------------- /test/helpers/latestTime.js: -------------------------------------------------------------------------------- 1 | // Returns the time of the last mined block in seconds 2 | export default function latestTime() { 3 | return web3.eth.getBlock('latest').timestamp; 4 | } 5 | -------------------------------------------------------------------------------- /test/helpers/toPromise.js: -------------------------------------------------------------------------------- 1 | export default func => 2 | (...args) => 3 | new Promise((accept, reject) => 4 | func(...args, (error, data) => error ? reject(error) : accept(data))); 5 | -------------------------------------------------------------------------------- /test/helpers/hashMessage.js: -------------------------------------------------------------------------------- 1 | import utils from 'ethereumjs-util'; 2 | 3 | // Hash and add same prefix to the hash that testrpc use. 4 | module.exports = function(message) { 5 | const messageHex = new Buffer(utils.sha3(message).toString('hex'), 'hex'); 6 | const prefix = utils.toBuffer('\u0019Ethereum Signed Message:\n' + messageHex.length.toString()); 7 | return utils.bufferToHex( utils.sha3(Buffer.concat([prefix, messageHex])) ); 8 | }; 9 | -------------------------------------------------------------------------------- /test/helpers/timer.js: -------------------------------------------------------------------------------- 1 | // timer for tests specific to testrpc 2 | module.exports = s => { 3 | return new Promise((resolve, reject) => { 4 | web3.currentProvider.sendAsync({ 5 | jsonrpc: '2.0', 6 | method: 'evm_increaseTime', 7 | params: [s], // 60 seaconds, may need to be hex, I forget 8 | id: new Date().getTime() // Id of the request; anything works, really 9 | }, function(err) { 10 | if (err) return reject(err); 11 | resolve(); 12 | }); 13 | //setTimeout(() => resolve(), s * 1000 + 600) // 600ms breathing room for testrpc to sync 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.2; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/Controlled.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | contract Controlled { 4 | /// @notice The address of the controller is the only address that can call 5 | /// a function with this modifier 6 | modifier onlyController { require(msg.sender == controller); _; } 7 | 8 | address public controller; 9 | 10 | function Controlled() public { controller = msg.sender;} 11 | 12 | /// @notice Changes the controller of the contract 13 | /// @param _newController The new controller of the contract 14 | function changeController(address _newController) public onlyController { 15 | controller = _newController; 16 | } 17 | } -------------------------------------------------------------------------------- /contracts/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | /** 4 | * @title ERC20 5 | * @dev ERC20 interface 6 | */ 7 | contract ERC20 { 8 | function balanceOf(address who) public constant returns (uint256); 9 | function transfer(address to, uint256 value) public returns (bool); 10 | function allowance(address owner, address spender) public constant returns (uint256); 11 | function transferFrom(address from, address to, uint256 value) public returns (bool); 12 | function approve(address spender, uint256 value) public returns (bool); 13 | event Transfer(address indexed from, address indexed to, uint256 value); 14 | event Approval(address indexed owner, address indexed spender, uint256 value); 15 | } -------------------------------------------------------------------------------- /test/helpers/advanceToBlock.js: -------------------------------------------------------------------------------- 1 | export function advanceBlock() { 2 | return new Promise((resolve, reject) => { 3 | web3.currentProvider.sendAsync({ 4 | jsonrpc: '2.0', 5 | method: 'evm_mine', 6 | id: Date.now(), 7 | }, (err, res) => { 8 | return err ? reject(err) : resolve(res) 9 | }) 10 | }) 11 | } 12 | 13 | // Advances the block number so that the last mined block is `number`. 14 | export default async function advanceToBlock(number) { 15 | if (web3.eth.blockNumber > number) { 16 | throw Error(`block number ${number} is in the past (current is ${web3.eth.blockNumber})`) 17 | } 18 | 19 | while (web3.eth.blockNumber < number) { 20 | await advanceBlock() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/iEthealSale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | /// @dev Crowdsale interface for Etheal Normal Sale, functions needed from outside. 4 | contract iEthealSale { 5 | bool public paused; 6 | uint256 public minContribution; 7 | uint256 public whitelistThreshold; 8 | mapping (address => uint256) public stakes; 9 | function setPromoBonus(address _investor, uint256 _value) public; 10 | function buyTokens(address _beneficiary) public payable; 11 | function depositEth(address _beneficiary, uint256 _time, bytes _whitelistSign) public payable; 12 | function depositOffchain(address _beneficiary, uint256 _amount, uint256 _time) public; 13 | function hasEnded() public constant returns (bool); 14 | } -------------------------------------------------------------------------------- /contracts/EthealToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./MiniMeToken.sol"; 4 | 5 | /** 6 | * @title EthealToken 7 | * @dev Basic MiniMe token 8 | */ 9 | contract EthealToken is MiniMeToken { 10 | function EthealToken(address _controller, address _tokenFactory) 11 | MiniMeToken( 12 | _tokenFactory, 13 | 0x0, // no parent token 14 | 0, // no snapshot block number from parent 15 | "Etheal Token", // Token name 16 | 18, // Decimals 17 | "HEAL", // Symbol 18 | true // Enable transfers 19 | ) 20 | { 21 | changeController(_controller); 22 | } 23 | } -------------------------------------------------------------------------------- /test/helpers/expectThrow.js: -------------------------------------------------------------------------------- 1 | export default async promise => { 2 | try { 3 | await promise; 4 | } catch (error) { 5 | // TODO: Check jump destination to destinguish between a throw 6 | // and an actual invalid jump. 7 | const invalidOpcode = error.message.search('invalid opcode') >= 0; 8 | // TODO: When we contract A calls contract B, and B throws, instead 9 | // of an 'invalid jump', we get an 'out of gas' error. How do 10 | // we distinguish this from an actual out of gas event? (The 11 | // testrpc log actually show an 'invalid jump' event.) 12 | const outOfGas = error.message.search('out of gas') >= 0; 13 | assert( 14 | invalidOpcode || outOfGas, 15 | "Expected throw, got '" + error + "' instead", 16 | ); 17 | return; 18 | } 19 | assert.fail('Expected throw not received'); 20 | }; 21 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('babel-polyfill'); 3 | 4 | var provider; 5 | var HDWalletProvider = require('truffle-hdwallet-provider'); 6 | var mnemonic = '[REDACTED]'; 7 | 8 | if (!process.env.SOLIDITY_COVERAGE){ 9 | provider = new HDWalletProvider(mnemonic, 'https://ropsten.infura.io/') 10 | } 11 | 12 | 13 | module.exports = { 14 | networks: { 15 | development: { 16 | host: 'localhost', 17 | port: 8545, 18 | network_id: '*', 19 | gas: 4600000 20 | }, 21 | ropsten: { 22 | provider: provider, 23 | network_id: 3 // official id of the ropsten network 24 | }, 25 | coverage: { 26 | host: "localhost", 27 | network_id: "*", 28 | port: 8555, 29 | gas: 0xfffffffffff, 30 | gasPrice: 0x0 31 | } 32 | }, 33 | solc: { 34 | optimizer: { 35 | enabled: true, 36 | runs: 200 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | 4 | /** 5 | * @title SafeMath 6 | * @dev Math operations with safety checks that throw on error 7 | */ 8 | library SafeMath { 9 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 10 | uint256 c = a * b; 11 | assert(a == 0 || c / a == b); 12 | return c; 13 | } 14 | 15 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 16 | // assert(b > 0); // Solidity automatically throws when dividing by 0 17 | uint256 c = a / b; 18 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 19 | return c; 20 | } 21 | 22 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 23 | assert(b <= a); 24 | return a - b; 25 | } 26 | 27 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 28 | uint256 c = a + b; 29 | assert(c >= a); 30 | return c; 31 | } 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethealtoken", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "truffle.js", 6 | "scripts": { 7 | "test": "scripts/test.sh", 8 | "console": "truffle console", 9 | "coverage": "scripts/coverage.sh" 10 | }, 11 | "author": "thesved", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "babel-polyfill": "^6.26.0", 15 | "babel-preset-es2015": "^6.24.1", 16 | "babel-preset-stage-2": "^6.24.1", 17 | "babel-preset-stage-3": "^6.24.1", 18 | "babel-register": "^6.26.0", 19 | "chai": "^4.1.2", 20 | "chai-as-promised": "^7.1.1", 21 | "chai-bignumber": "^2.0.2", 22 | "coveralls": "^3.0.0", 23 | "ethereumjs-testrpc": "^6.0.3", 24 | "mocha-lcov-reporter": "^1.3.0", 25 | "solidity-coverage": "^0.4.11", 26 | "truffle": "^4.1.3", 27 | "truffle-hdwallet-provider": "^0.0.3", 28 | "ganache-cli": "^6.1.0" 29 | }, 30 | "dependencies": { 31 | "zeppelin-solidity": "^1.3.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/HasNoTokens.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./Ownable.sol"; 4 | import "./ERC20.sol"; 5 | 6 | /** 7 | * @title claim accidentally sent tokens 8 | */ 9 | contract HasNoTokens is Ownable { 10 | event ExtractedTokens(address indexed _token, address indexed _claimer, uint _amount); 11 | 12 | /// @notice This method can be used to extract mistakenly 13 | /// sent tokens to this contract. 14 | /// @param _token The address of the token contract that you want to recover 15 | /// set to 0 in case you want to extract ether. 16 | /// @param _claimer Address that tokens will be send to 17 | function extractTokens(address _token, address _claimer) onlyOwner public { 18 | if (_token == 0x0) { 19 | _claimer.transfer(this.balance); 20 | return; 21 | } 22 | 23 | ERC20 token = ERC20(_token); 24 | uint balance = token.balanceOf(this); 25 | token.transfer(_claimer, balance); 26 | ExtractedTokens(_token, _claimer, balance); 27 | } 28 | } -------------------------------------------------------------------------------- /contracts/FinalizableCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './SafeMath.sol'; 4 | import './Ownable.sol'; 5 | import './Crowdsale.sol'; 6 | 7 | /** 8 | * @title FinalizableCrowdsale 9 | * @dev Extension of Crowdsale where an owner can do extra work 10 | * after finishing. 11 | */ 12 | contract FinalizableCrowdsale is Crowdsale, Ownable { 13 | using SafeMath for uint256; 14 | 15 | bool public isFinalized = false; 16 | 17 | event Finalized(); 18 | 19 | /** 20 | * @dev Must be called after crowdsale ends, to do some extra finalization 21 | * work. Calls the contract's finalization function. 22 | */ 23 | function finalize() onlyOwner public { 24 | require(!isFinalized); 25 | require(hasEnded()); 26 | 27 | finalization(); 28 | Finalized(); 29 | 30 | isFinalized = true; 31 | } 32 | 33 | /** 34 | * @dev Can be overridden to add finalization logic. The overriding function 35 | * should call super.finalization() to ensure the chain of finalization is 36 | * executed entirely. 37 | */ 38 | function finalization() internal { 39 | } 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contracts/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | 4 | import "./Ownable.sol"; 5 | 6 | 7 | /** 8 | * @title Pausable 9 | * @dev Base contract which allows children to implement an emergency stop mechanism. 10 | */ 11 | contract Pausable is Ownable { 12 | event Pause(); 13 | event Unpause(); 14 | 15 | bool public paused = false; 16 | 17 | 18 | /** 19 | * @dev Modifier to make a function callable only when the contract is not paused. 20 | */ 21 | modifier whenNotPaused() { 22 | require(!paused); 23 | _; 24 | } 25 | 26 | /** 27 | * @dev Modifier to make a function callable only when the contract is paused. 28 | */ 29 | modifier whenPaused() { 30 | require(paused); 31 | _; 32 | } 33 | 34 | /** 35 | * @dev called by the owner to pause, triggers stopped state 36 | */ 37 | function pause() onlyOwner whenNotPaused public { 38 | paused = true; 39 | Pause(); 40 | } 41 | 42 | /** 43 | * @dev called by the owner to unpause, returns to normal state 44 | */ 45 | function unpause() onlyOwner whenPaused public { 46 | paused = false; 47 | Unpause(); 48 | } 49 | } -------------------------------------------------------------------------------- /test/helpers/transactionMined.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //from https://gist.github.com/xavierlepretre/88682e871f4ad07be4534ae560692ee6 4 | module.export = web3.eth.transactionMined = function (txnHash, interval) { 5 | var transactionReceiptAsync; 6 | interval = interval ? interval : 500; 7 | transactionReceiptAsync = function(txnHash, resolve, reject) { 8 | try { 9 | var receipt = web3.eth.getTransactionReceipt(txnHash); 10 | if (receipt === null) { 11 | setTimeout(function () { 12 | transactionReceiptAsync(txnHash, resolve, reject); 13 | }, interval); 14 | } else { 15 | resolve(receipt); 16 | } 17 | } catch(e) { 18 | reject(e); 19 | } 20 | }; 21 | 22 | if (Array.isArray(txnHash)) { 23 | var promises = []; 24 | txnHash.forEach(function (oneTxHash) { 25 | promises.push( 26 | web3.eth.getTransactionReceiptMined(oneTxHash, interval)); 27 | }); 28 | return Promise.all(promises); 29 | } else { 30 | return new Promise(function (resolve, reject) { 31 | transactionReceiptAsync(txnHash, resolve, reject); 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | 4 | /** 5 | * @title Ownable 6 | * @dev The Ownable contract has an owner address, and provides basic authorization control 7 | * functions, this simplifies the implementation of "user permissions". 8 | */ 9 | contract Ownable { 10 | address public owner; 11 | 12 | 13 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 14 | 15 | 16 | /** 17 | * @dev The Ownable constructor sets the original `owner` of the contract to the sender 18 | * account. 19 | */ 20 | function Ownable() { 21 | owner = msg.sender; 22 | } 23 | 24 | 25 | /** 26 | * @dev Throws if called by any account other than the owner. 27 | */ 28 | modifier onlyOwner() { 29 | require(msg.sender == owner); 30 | _; 31 | } 32 | 33 | 34 | /** 35 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 36 | * @param newOwner The address to transfer ownership to. 37 | */ 38 | function transferOwnership(address newOwner) onlyOwner public { 39 | require(newOwner != address(0)); 40 | OwnershipTransferred(owner, newOwner); 41 | owner = newOwner; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /contracts/ERC20MiniMe.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | 4 | import './ERC20.sol'; 5 | import "./Controlled.sol"; 6 | 7 | /** 8 | * @title MiniMe interface 9 | * @dev see https://github.com/ethereum/EIPs/issues/20 10 | */ 11 | contract ERC20MiniMe is ERC20, Controlled { 12 | function approveAndCall(address _spender, uint256 _amount, bytes _extraData) public returns (bool); 13 | function totalSupply() public constant returns (uint); 14 | function balanceOfAt(address _owner, uint _blockNumber) public constant returns (uint); 15 | function totalSupplyAt(uint _blockNumber) public constant returns(uint); 16 | function createCloneToken(string _cloneTokenName, uint8 _cloneDecimalUnits, string _cloneTokenSymbol, uint _snapshotBlock, bool _transfersEnabled) public returns(address); 17 | function generateTokens(address _owner, uint _amount) public returns (bool); 18 | function destroyTokens(address _owner, uint _amount) public returns (bool); 19 | function enableTransfers(bool _transfersEnabled) public; 20 | function isContract(address _addr) constant internal returns(bool); 21 | function claimTokens(address _token) public; 22 | event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount); 23 | event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock); 24 | } -------------------------------------------------------------------------------- /contracts/ECRecovery.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | /** 5 | * @title Eliptic curve signature operations 6 | * 7 | * @dev Based on https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d 8 | */ 9 | 10 | library ECRecovery { 11 | 12 | /** 13 | * @dev Recover signer address from a message by using his signature 14 | * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address. 15 | * @param sig bytes signature, the signature is generated using web3.eth.sign() 16 | */ 17 | function recover(bytes32 hash, bytes sig) public pure returns (address) { 18 | bytes32 r; 19 | bytes32 s; 20 | uint8 v; 21 | 22 | //Check the signature length 23 | if (sig.length != 65) { 24 | return (address(0)); 25 | } 26 | 27 | // Divide the signature in r, s and v variables 28 | assembly { 29 | r := mload(add(sig, 32)) 30 | s := mload(add(sig, 64)) 31 | v := byte(0, mload(add(sig, 96))) 32 | } 33 | 34 | // Version of signature should be 27 or 28, but 0 and 1 are also possible versions 35 | if (v < 27) { 36 | v += 27; 37 | } 38 | 39 | // If the version is correct return the signer address 40 | if (v != 27 && v != 28) { 41 | return (address(0)); 42 | } else { 43 | return ecrecover(hash, v, r, s); 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /contracts/CappedCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './SafeMath.sol'; 4 | import './Crowdsale.sol'; 5 | 6 | /** 7 | * @title CappedCrowdsale 8 | * @dev Extension of Crowdsale with a max amount of funds raised 9 | */ 10 | contract CappedCrowdsale is Crowdsale { 11 | using SafeMath for uint256; 12 | 13 | uint256 public cap; 14 | 15 | function CappedCrowdsale(uint256 _cap) { 16 | require(_cap > 0); 17 | cap = _cap; 18 | } 19 | 20 | // overriding Crowdsale#validPurchase to add extra cap logic 21 | // @return true if investors can buy at the moment 22 | function validPurchase(uint256 weiAmount) internal constant returns (bool) { 23 | return super.validPurchase(weiAmount) && !capReached(); 24 | } 25 | 26 | // overriding Crowdsale#hasEnded to add cap logic 27 | // @return true if crowdsale event has ended 28 | function hasEnded() public constant returns (bool) { 29 | return super.hasEnded() || capReached(); 30 | } 31 | 32 | // @return true if cap has been reached 33 | function capReached() internal constant returns (bool) { 34 | return weiRaised >= cap; 35 | } 36 | 37 | // overriding Crowdsale#buyTokens to add partial refund logic 38 | function buyTokens(address beneficiary) public payable { 39 | uint256 weiToCap = cap.sub(weiRaised); 40 | uint256 weiAmount = weiToCap < msg.value ? weiToCap : msg.value; 41 | 42 | buyTokens(beneficiary, weiAmount); 43 | 44 | uint256 refund = msg.value.sub(weiAmount); 45 | if (refund > 0) { 46 | msg.sender.transfer(refund); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /test/helpers/increaseTime.js: -------------------------------------------------------------------------------- 1 | import latestTime from './latestTime' 2 | 3 | // Increases testrpc time by the passed duration in seconds 4 | export default function increaseTime(duration) { 5 | const id = Date.now() 6 | 7 | return new Promise((resolve, reject) => { 8 | web3.currentProvider.sendAsync({ 9 | jsonrpc: '2.0', 10 | method: 'evm_increaseTime', 11 | params: [duration], 12 | id: id, 13 | }, err1 => { 14 | if (err1) return reject(err1) 15 | 16 | web3.currentProvider.sendAsync({ 17 | jsonrpc: '2.0', 18 | method: 'evm_mine', 19 | id: id+1, 20 | }, (err2, res) => { 21 | return err2 ? reject(err2) : resolve(res) 22 | }) 23 | }) 24 | }) 25 | } 26 | 27 | /** 28 | * Beware that due to the need of calling two separate testrpc methods and rpc calls overhead 29 | * it's hard to increase time precisely to a target point so design your test to tolerate 30 | * small fluctuations from time to time. 31 | * 32 | * @param target time in seconds 33 | */ 34 | export function increaseTimeTo(target) { 35 | let now = latestTime(); 36 | if (target < now) throw Error(`Cannot increase current time(${now}) to a moment in the past(${target})`); 37 | let diff = target - now; 38 | return increaseTime(diff); 39 | } 40 | 41 | export const duration = { 42 | seconds: function(val) { return val}, 43 | minutes: function(val) { return val * this.seconds(60) }, 44 | hours: function(val) { return val * this.minutes(60) }, 45 | days: function(val) { return val * this.hours(24) }, 46 | weeks: function(val) { return val * this.days(7) }, 47 | years: function(val) { return val * this.days(365)} 48 | }; -------------------------------------------------------------------------------- /contracts/RefundVault.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './SafeMath.sol'; 4 | import './Ownable.sol'; 5 | import './HasNoTokens.sol'; 6 | 7 | /** 8 | * @title RefundVault 9 | * @dev This contract is used for storing funds while a crowdsale 10 | * is in progress. Supports refunding the money if crowdsale fails, 11 | * and forwarding it if crowdsale is successful. 12 | */ 13 | contract RefundVault is Ownable, HasNoTokens { 14 | using SafeMath for uint256; 15 | 16 | enum State { Active, Refunding, Closed } 17 | 18 | mapping (address => uint256) public deposited; 19 | address public wallet; 20 | State public state; 21 | 22 | event Closed(); 23 | event RefundsEnabled(); 24 | event Refunded(address indexed beneficiary, uint256 weiAmount); 25 | 26 | function RefundVault(address _wallet) { 27 | require(_wallet != 0x0); 28 | wallet = _wallet; 29 | state = State.Active; 30 | } 31 | 32 | function deposit(address investor) onlyOwner public payable { 33 | require(state == State.Active); 34 | deposited[investor] = deposited[investor].add(msg.value); 35 | } 36 | 37 | function close() onlyOwner public { 38 | require(state == State.Active); 39 | state = State.Closed; 40 | Closed(); 41 | wallet.transfer(this.balance); 42 | } 43 | 44 | function enableRefunds() onlyOwner public { 45 | require(state == State.Active); 46 | state = State.Refunding; 47 | RefundsEnabled(); 48 | } 49 | 50 | function refund(address investor) public { 51 | require(state == State.Refunding); 52 | uint256 depositedValue = deposited[investor]; 53 | deposited[investor] = 0; 54 | investor.transfer(depositedValue); 55 | Refunded(investor, depositedValue); 56 | } 57 | } -------------------------------------------------------------------------------- /contracts/TokenController.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./ERC20MiniMe.sol"; 4 | 5 | /// @dev The token controller contract must implement these functions 6 | contract TokenController { 7 | ERC20MiniMe public ethealToken; 8 | address public SALE; // address where sale tokens are located 9 | 10 | /// @notice needed for hodler handling 11 | function addHodlerStake(address _beneficiary, uint256 _stake) public; 12 | function setHodlerStake(address _beneficiary, uint256 _stake) public; 13 | function setHodlerTime(uint256 _time) public; 14 | 15 | 16 | /// @notice Called when `_owner` sends ether to the MiniMe Token contract 17 | /// @param _owner The address that sent the ether to create tokens 18 | /// @return True if the ether is accepted, false if it throws 19 | function proxyPayment(address _owner) public payable returns(bool); 20 | 21 | /// @notice Notifies the controller about a token transfer allowing the 22 | /// controller to react if desired 23 | /// @param _from The origin of the transfer 24 | /// @param _to The destination of the transfer 25 | /// @param _amount The amount of the transfer 26 | /// @return False if the controller does not authorize the transfer 27 | function onTransfer(address _from, address _to, uint _amount) public returns(bool); 28 | 29 | /// @notice Notifies the controller about an approval allowing the 30 | /// controller to react if desired 31 | /// @param _owner The address that calls `approve()` 32 | /// @param _spender The spender in the `approve()` call 33 | /// @param _amount The amount in the `approve()` call 34 | /// @return False if the controller does not authorize the approval 35 | function onApprove(address _owner, address _spender, uint _amount) public returns(bool); 36 | } -------------------------------------------------------------------------------- /contracts/RefundableCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | 4 | import './SafeMath.sol'; 5 | import './FinalizableCrowdsale.sol'; 6 | import './RefundVault.sol'; 7 | 8 | 9 | /** 10 | * @title RefundableCrowdsale 11 | * @dev Extension of Crowdsale contract that adds a funding goal, and 12 | * the possibility of users getting a refund if goal is not met. 13 | * Uses a RefundVault as the crowdsale's vault. 14 | */ 15 | contract RefundableCrowdsale is FinalizableCrowdsale { 16 | using SafeMath for uint256; 17 | 18 | // minimum amount of funds to be raised in weis 19 | uint256 public goal; 20 | 21 | // refund vault used to hold funds while crowdsale is running 22 | RefundVault public vault; 23 | 24 | function RefundableCrowdsale(uint256 _goal) { 25 | require(_goal > 0); 26 | vault = new RefundVault(wallet); 27 | goal = _goal; 28 | } 29 | 30 | // We're overriding the fund forwarding from Crowdsale. 31 | // If the goal is reached forward the fund to the wallet, 32 | // otherwise in addition to sending the funds, we want to 33 | // call the RefundVault deposit function 34 | function forwardFunds(uint256 weiAmount) internal { 35 | if (goalReached()) 36 | wallet.transfer(weiAmount); 37 | else 38 | vault.deposit.value(weiAmount)(msg.sender); 39 | } 40 | 41 | // if crowdsale is unsuccessful, investors can claim refunds here 42 | function claimRefund() public { 43 | require(isFinalized); 44 | require(!goalReached()); 45 | 46 | vault.refund(msg.sender); 47 | } 48 | 49 | // vault finalization task, called when owner calls finalize() 50 | function finalization() internal { 51 | if (goalReached()) { 52 | vault.close(); 53 | } else { 54 | vault.enableRefunds(); 55 | } 56 | 57 | super.finalization(); 58 | } 59 | 60 | function goalReached() public constant returns (bool) { 61 | return weiRaised >= goal; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /contracts/EthealWhitelist.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./Ownable.sol"; 4 | import "./ECRecovery.sol"; 5 | 6 | /** 7 | * @title EthealWhitelist 8 | * @author thesved 9 | * @notice EthealWhitelist contract which handles KYC 10 | */ 11 | contract EthealWhitelist is Ownable { 12 | using ECRecovery for bytes32; 13 | 14 | // signer address for offchain whitelist signing 15 | address public signer; 16 | 17 | // storing whitelisted addresses 18 | mapping(address => bool) public isWhitelisted; 19 | 20 | event WhitelistSet(address indexed _address, bool _state); 21 | 22 | //////////////// 23 | // Constructor 24 | //////////////// 25 | function EthealWhitelist(address _signer) { 26 | require(_signer != address(0)); 27 | 28 | signer = _signer; 29 | } 30 | 31 | /// @notice set signing address after deployment 32 | function setSigner(address _signer) public onlyOwner { 33 | require(_signer != address(0)); 34 | 35 | signer = _signer; 36 | } 37 | 38 | //////////////// 39 | // Whitelisting: only owner 40 | //////////////// 41 | 42 | /// @notice Set whitelist state for an address. 43 | function setWhitelist(address _addr, bool _state) public onlyOwner { 44 | require(_addr != address(0)); 45 | isWhitelisted[_addr] = _state; 46 | WhitelistSet(_addr, _state); 47 | } 48 | 49 | /// @notice Set whitelist state for multiple addresses 50 | function setManyWhitelist(address[] _addr, bool _state) public onlyOwner { 51 | for (uint256 i = 0; i < _addr.length; i++) { 52 | setWhitelist(_addr[i], _state); 53 | } 54 | } 55 | 56 | /// @notice offchain whitelist check 57 | function isOffchainWhitelisted(address _addr, bytes _sig) public view returns (bool) { 58 | bytes32 hash = keccak256("\x19Ethereum Signed Message:\n20",_addr); 59 | return hash.recover(_sig) == signer; 60 | } 61 | } -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit script as soon as a command fails. 4 | set -o errexit 5 | 6 | # Executes cleanup function at script exit. 7 | trap cleanup EXIT 8 | 9 | cleanup() { 10 | # Kill the testrpc instance that we started (if we started one and if it's still running). 11 | echo "cleanup: $testrpc_pid" 12 | if [ -n "$testrpc_pid" ] && ps -p $testrpc_pid > /dev/null; then 13 | echo "kill!" 14 | kill -9 $testrpc_pid 15 | #kill -9 $(ps aux | grep "[t]estrpc" | awk '{print $2}') 16 | fi 17 | } 18 | 19 | if [ "$SOLIDITY_COVERAGE" = true ]; then 20 | testrpc_port=8555 21 | else 22 | testrpc_port=8545 23 | fi 24 | 25 | testrpc_running() { 26 | nc -z localhost "$testrpc_port" 27 | } 28 | 29 | testrpc() { 30 | if [ "$SOLIDITY_COVERAGE" = true ]; then 31 | node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --gasPrice 0 --port "$testrpc_port" "$@" 32 | else 33 | node_modules/.bin/testrpc --gasLimit 0xfffffffffff --gasPrice 0 "$@" 34 | fi 35 | } 36 | 37 | if testrpc_running; then 38 | echo "Using existing testrpc instance" 39 | else 40 | echo "Starting our own testrpc instance" 41 | # We define 10 accounts with balance 1M ether, needed for high-value tests. 42 | testrpc \ 43 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200,1000000000000000000000000" \ 44 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501201,1000000000000000000000000" \ 45 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501202,1000000000000000000000000" \ 46 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501203,1000000000000000000000000" \ 47 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501204,1000000000000000000000000" \ 48 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501205,1000000000000000000000000" \ 49 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501206,1000000000000000000000000" \ 50 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501207,1000000000000000000000000" \ 51 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501208,1000000000000000000000000" \ 52 | --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" \ 53 | > /dev/null 2> /dev/null & #may want to remove the 2> devnull part if you want to see errors for some reason 54 | testrpc_pid=$(($!+2)) #somehow for my mac the eventual pid becomes +2 compared to $! 55 | fi 56 | 57 | echo "truffle start, $testrpc_pid" 58 | node_modules/.bin/truffle version 59 | 60 | if [ "$SOLIDITY_COVERAGE" = true ]; then 61 | node_modules/.bin/solidity-coverage 62 | 63 | if [ "$CONTINUOUS_INTEGRATION" = true ]; then 64 | cat coverage/lcov.info | node_modules/.bin/coveralls 65 | fi 66 | else 67 | node_modules/.bin/truffle test "$@" 68 | fi 69 | -------------------------------------------------------------------------------- /contracts/Crowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './ERC20MiniMe.sol'; 4 | import './SafeMath.sol'; 5 | 6 | /** 7 | * @title Crowdsale 8 | * @dev Crowdsale is a base contract for managing a token crowdsale. 9 | * Crowdsales have a start and end timestamps, where investors can make 10 | * token purchases and the crowdsale will assign them tokens based 11 | * on a token per ETH rate. Funds collected are forwarded to a wallet 12 | * as they arrive. 13 | */ 14 | contract Crowdsale { 15 | using SafeMath for uint256; 16 | 17 | // The token being sold 18 | ERC20MiniMe public token; 19 | 20 | // start and end timestamps where investments are allowed (both inclusive) 21 | uint256 public startTime; 22 | uint256 public endTime; 23 | 24 | // address where funds are collected 25 | address public wallet; 26 | 27 | // how many token units a buyer gets per wei 28 | uint256 public rate; 29 | 30 | // amount of raised money in wei 31 | uint256 public weiRaised; 32 | 33 | /** 34 | * event for token purchase logging 35 | * @param purchaser who paid for the tokens 36 | * @param beneficiary who got the tokens 37 | * @param value weis paid for purchase 38 | * @param amount amount of tokens purchased 39 | */ 40 | event TokenPurchase(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount); 41 | 42 | 43 | function Crowdsale(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet) { 44 | require(_startTime >= now); 45 | require(_endTime >= _startTime); 46 | require(_rate > 0); 47 | require(_wallet != 0x0); 48 | 49 | startTime = _startTime; 50 | endTime = _endTime; 51 | rate = _rate; 52 | wallet = _wallet; 53 | } 54 | 55 | 56 | // fallback function can be used to buy tokens 57 | function () payable { 58 | buyTokens(msg.sender); 59 | } 60 | 61 | // low level token purchase function 62 | function buyTokens(address beneficiary) public payable { 63 | buyTokens(beneficiary, msg.value); 64 | } 65 | 66 | // implementation of low level token purchase function 67 | function buyTokens(address beneficiary, uint256 weiAmount) internal { 68 | require(beneficiary != 0x0); 69 | require(validPurchase(weiAmount)); 70 | 71 | transferToken(beneficiary, weiAmount); 72 | 73 | // update state 74 | weiRaised = weiRaised.add(weiAmount); 75 | 76 | forwardFunds(weiAmount); 77 | } 78 | 79 | // low level transfer token 80 | // override to create custom token transfer mechanism, eg. pull pattern 81 | function transferToken(address beneficiary, uint256 weiAmount) internal { 82 | // calculate token amount to be created 83 | uint256 tokens = weiAmount.mul(rate); 84 | 85 | token.generateTokens(beneficiary, tokens); 86 | 87 | TokenPurchase(msg.sender, beneficiary, weiAmount, tokens); 88 | } 89 | 90 | // send ether to the fund collection wallet 91 | // override to create custom fund forwarding mechanisms 92 | function forwardFunds(uint256 weiAmount) internal { 93 | wallet.transfer(weiAmount); 94 | } 95 | 96 | // @return true if the transaction can buy tokens 97 | function validPurchase(uint256 weiAmount) internal constant returns (bool) { 98 | bool withinPeriod = now >= startTime && now <= endTime; 99 | bool nonZeroPurchase = weiAmount != 0; 100 | return withinPeriod && nonZeroPurchase; 101 | } 102 | 103 | // @return true if crowdsale event has ended 104 | function hasEnded() public constant returns (bool) { 105 | return now > endTime; 106 | } 107 | 108 | // @return true if crowdsale has started 109 | function hasStarted() public constant returns (bool) { 110 | return now >= startTime; 111 | } 112 | } -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var MiniMeTokenFactory = artifacts.require("MiniMeTokenFactory"); 2 | var EthealToken = artifacts.require("EthealToken"); 3 | var Wallet = artifacts.require("MultiSigWallet"); 4 | var PreSale = artifacts.require("EthealPreSale"); 5 | var NormalSale = artifacts.require("EthealNormalSale"); 6 | var SafeMath = artifacts.require("SafeMath"); 7 | var EthealController = artifacts.require("EthealController"); 8 | var RefundVault = artifacts.require("RefundVault"); 9 | var Hodler = artifacts.require("Hodler"); 10 | var TokenVesting = artifacts.require("TokenVesting"); 11 | var EthealDeposit = artifacts.require("EthealDeposit"); 12 | var EthealWhitelist = artifacts.require("EthealWhitelist"); 13 | var EthealPromoToken = artifacts.require("EthealPromoToken"); 14 | var ECRecovery = artifacts.require("ECRecovery"); 15 | var ED,EN,EW; 16 | 17 | var dateStart = Math.floor(new Date().getTime()/1000)+30*60; // starts in 30 minutes 18 | var dateEnd = dateStart + 10*24*60*60; // lasts 10 days 19 | 20 | module.exports = function(deployer) { 21 | return deployer.then(function(){ 22 | // deploy SafeMath first 23 | return deployer.deploy(SafeMath); 24 | }).then(function(){ 25 | // link SafeMath 26 | return deployer.link(SafeMath, [PreSale, NormalSale, EthealController, Hodler, TokenVesting, EthealDeposit, EthealWhitelist, EthealPromoToken]); 27 | }).then(function(){ 28 | // deploy SafeMath first 29 | return deployer.deploy(ECRecovery); 30 | }).then(function(){ 31 | // link SafeMath 32 | return deployer.link(ECRecovery, [EthealWhitelist]); 33 | }).then(function(){ 34 | // then Wallet 35 | return deployer.deploy(Wallet,[web3.eth.accounts[0],web3.eth.accounts[1],web3.eth.accounts[2]],2); 36 | }).then(function(){ 37 | // then Factory 38 | return deployer.deploy(MiniMeTokenFactory); 39 | }).then(function(){ 40 | // then Controller 41 | return deployer.deploy(EthealController,Wallet.address); 42 | }).then(function(){ 43 | // then EthealToken 44 | return deployer.deploy(EthealToken,EthealController.address,MiniMeTokenFactory.address); 45 | }).then(function(){ 46 | // set Token for Crowdsale 47 | return (EthealController.at(EthealController.address)).setEthealToken(EthealToken.address,0); 48 | }).then(function(){ 49 | // promo token 50 | return deployer.deploy(EthealPromoToken,0); 51 | }).then(function(){ 52 | // whitelist 53 | return deployer.deploy(EthealWhitelist,web3.eth.accounts[4]); 54 | }).then(function(){ 55 | // then Normal Sale 56 | return deployer.deploy(NormalSale,EthealController.address,dateStart,dateEnd,web3.toWei(0.1, "ether"),1000,web3.toWei(10, "ether"),120*60*60,web3.toWei(50, "ether"),Wallet.address); 57 | }).then(function(){ 58 | // set crowdsale 59 | return (EthealController.at(EthealController.address)).setCrowdsaleTransfer(NormalSale.address,web3.toBigNumber(web3.toWei(50,"ether")).mul(1000)); 60 | }).then(function(){ 61 | // set promo token 62 | return (NormalSale.at(NormalSale.address)).setPromoTokenController(EthealPromoToken.address); 63 | }).then(function(){ 64 | // set whitelist 65 | return (NormalSale.at(NormalSale.address)).setWhitelist(EthealWhitelist.address, web3.toWei(1, "ether")); 66 | }).then(function(){ 67 | // promo controller set sale addr 68 | return (EthealPromoToken.at(EthealPromoToken.address)).setCrowdsale(NormalSale.address); 69 | }).then(function(){ 70 | // etheal deposit 71 | return deployer.deploy(EthealDeposit,NormalSale.address,EthealWhitelist.address); 72 | }).then(function(){ 73 | // set deposit 74 | return (NormalSale.at(NormalSale.address)).setDeposit(EthealDeposit.address); 75 | })/*.then(function(){ 76 | // contribute 77 | return EthealDeposit.at(EthealDeposit.address).deposit(web3.eth.accounts[1],"0xa36ceb1eb4acda877cbedf5ffa18d791944739d4a4e8d38bdbe9956af9bbca81693cef62d2f5aee6153bda31e2ad3b2d5061a8f26177042fca55bc917ba29b9400",{value:web3.toWei(0.1,"ether")}); 78 | })*/; 79 | }; -------------------------------------------------------------------------------- /contracts/TokenVesting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './ERC20MiniMe.sol'; 4 | import './Ownable.sol'; 5 | import './SafeMath.sol'; 6 | 7 | /** 8 | * @title TokenVesting 9 | * @dev A token holder contract that can release its token balance gradually like a 10 | * typical vesting scheme, with a cliff and vesting period. Optionally revocable by the 11 | * owner. 12 | */ 13 | contract TokenVesting is Ownable { 14 | using SafeMath for uint256; 15 | 16 | event Released(uint256 amount); 17 | event Revoked(); 18 | 19 | // beneficiary of tokens after they are released 20 | address public beneficiary; 21 | 22 | uint256 public cliff; 23 | uint256 public start; 24 | uint256 public duration; 25 | 26 | bool public revocable; 27 | 28 | mapping (address => uint256) public released; 29 | mapping (address => bool) public revoked; 30 | 31 | /** 32 | * @dev Creates a vesting contract that vests its balance of any ERC20 token to the 33 | * _beneficiary, gradually in a linear fashion until _start + _duration. By then all 34 | * of the balance will have vested. 35 | * @param _beneficiary address of the beneficiary to whom vested tokens are transferred 36 | * @param _cliff duration in seconds of the cliff in which tokens will begin to vest 37 | * @param _duration duration in seconds of the period in which the tokens will vest 38 | * @param _revocable whether the vesting is revocable or not 39 | */ 40 | function TokenVesting(address _beneficiary, uint256 _start, uint256 _cliff, uint256 _duration, bool _revocable) { 41 | require(_beneficiary != address(0)); 42 | require(_cliff <= _duration); 43 | 44 | beneficiary = _beneficiary; 45 | revocable = _revocable; 46 | duration = _duration; 47 | cliff = _start.add(_cliff); 48 | start = _start; 49 | } 50 | 51 | /** 52 | * @notice Transfers vested tokens to beneficiary. 53 | * @param token ERC20 token which is being vested 54 | */ 55 | function release(ERC20MiniMe token) public { 56 | uint256 unreleased = releasableAmount(token); 57 | 58 | require(unreleased > 0); 59 | 60 | released[token] = released[token].add(unreleased); 61 | 62 | require(token.transfer(beneficiary, unreleased)); 63 | 64 | Released(unreleased); 65 | } 66 | 67 | /** 68 | * @notice Allows the owner to revoke the vesting. Tokens already vested 69 | * remain in the contract, the rest are returned to the owner. 70 | * @param token ERC20MiniMe token which is being vested 71 | */ 72 | function revoke(ERC20MiniMe token) public onlyOwner { 73 | require(revocable); 74 | require(!revoked[token]); 75 | 76 | uint256 balance = token.balanceOf(this); 77 | 78 | uint256 unreleased = releasableAmount(token); 79 | uint256 refund = balance.sub(unreleased); 80 | 81 | revoked[token] = true; 82 | 83 | require(token.transfer(owner, refund)); 84 | 85 | Revoked(); 86 | } 87 | 88 | /** 89 | * @dev Calculates the amount that has already vested but hasn't been released yet. 90 | * @param token ERC20MiniMe token which is being vested 91 | */ 92 | function releasableAmount(ERC20MiniMe token) public constant returns (uint256) { 93 | return vestedAmount(token).sub(released[token]); 94 | } 95 | 96 | /** 97 | * @dev Calculates the amount that has already vested. 98 | * @param token ERC20MiniMe token which is being vested 99 | */ 100 | function vestedAmount(ERC20MiniMe token) public constant returns (uint256) { 101 | uint256 currentBalance = token.balanceOf(this); 102 | uint256 totalBalance = currentBalance.add(released[token]); 103 | 104 | if (now < cliff) { 105 | return 0; 106 | } else if (now >= start.add(duration) || revoked[token]) { 107 | return totalBalance; 108 | } else { 109 | return totalBalance.mul(now.sub(start)).div(duration); 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /contracts/EthealPromoToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.16; 2 | 3 | import "./HasNoTokens.sol"; 4 | import "./AbstractVirtualToken.sol"; 5 | import "./iEthealSale.sol"; 6 | 7 | /** 8 | * Etheal Promo ERC-20 contract 9 | * Author: thesved 10 | */ 11 | contract EthealPromoToken is HasNoTokens, AbstractVirtualToken { 12 | // Balance threshold to assign virtual tokens to the owner of higher balances then this threshold. 13 | uint256 private constant VIRTUAL_THRESHOLD = 0.1 ether; 14 | 15 | // Number of virtual tokens to assign to the owners of balances higher than virtual threshold. 16 | uint256 private constant VIRTUAL_COUNT = 911; 17 | 18 | // crowdsale to set bonus when sending token 19 | iEthealSale public crowdsale; 20 | 21 | // logging promo token activation 22 | event LogBonusSet(address indexed _address, uint256 _amount); 23 | 24 | //////////////// 25 | // Basic functions 26 | //////////////// 27 | 28 | /// @dev Constructor, crowdsale address can be 0x0 29 | function EthealPromoToken(address _crowdsale) { 30 | crowdsale = iEthealSale(_crowdsale); 31 | } 32 | 33 | /// @dev Setting crowdsale, crowdsale address can be 0x0 34 | function setCrowdsale(address _crowdsale) public onlyOwner { 35 | crowdsale = iEthealSale(_crowdsale); 36 | } 37 | 38 | /// @notice Get virtual balance of the owner of given address. 39 | /// @param _owner address to get virtual balance for the owner 40 | /// @return virtual balance of the owner of given address 41 | function virtualBalanceOf(address _owner) internal view returns (uint256) { 42 | return _owner.balance >= VIRTUAL_THRESHOLD ? VIRTUAL_COUNT : 0; 43 | } 44 | 45 | /// @notice Get name of this token. 46 | function name() public pure returns (string result) { 47 | return "An Etheal Promo"; 48 | } 49 | 50 | /// @notice Get symbol of this token. 51 | function symbol() public pure returns (string result) { 52 | return "HEALP"; 53 | } 54 | 55 | /// @notice Get number of decimals for this token. 56 | function decimals() public pure returns (uint8 result) { 57 | return 0; 58 | } 59 | 60 | 61 | //////////////// 62 | // Set sale bonus 63 | //////////////// 64 | 65 | /// @dev Internal function for setting sale bonus 66 | function setSaleBonus(address _from, address _to, uint256 _value) internal { 67 | if (address(crowdsale) == address(0)) return; 68 | if (_value == 0) return; 69 | 70 | if (_to == address(1) || _to == address(this) || _to == address(crowdsale)) { 71 | crowdsale.setPromoBonus(_from, _value); 72 | LogBonusSet(_from, _value); 73 | } 74 | } 75 | 76 | /// @dev Override transfer function to set sale bonus 77 | function transfer(address _to, uint256 _value) public returns (bool) { 78 | bool success = super.transfer(_to, _value); 79 | 80 | if (success) { 81 | setSaleBonus(msg.sender, _to, _value); 82 | } 83 | 84 | return success; 85 | } 86 | 87 | /// @dev Override transfer function to set sale bonus 88 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 89 | bool success = super.transferFrom(_from, _to, _value); 90 | 91 | if (success) { 92 | setSaleBonus(_from, _to, _value); 93 | } 94 | 95 | return success; 96 | } 97 | 98 | 99 | //////////////// 100 | // Extra 101 | //////////////// 102 | 103 | /// @notice Notify owners about their virtual balances. 104 | function massNotify(address[] _owners) public onlyOwner { 105 | for (uint256 i = 0; i < _owners.length; i++) { 106 | Transfer(address(0), _owners[i], VIRTUAL_COUNT); 107 | } 108 | } 109 | 110 | /// @notice Kill this smart contract. 111 | function kill() public onlyOwner { 112 | selfdestruct(owner); 113 | } 114 | 115 | 116 | } -------------------------------------------------------------------------------- /contracts/Hodler.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './TokenController.sol'; 4 | import './SafeMath.sol'; 5 | import './Ownable.sol'; 6 | 7 | /** 8 | * @title Hodler 9 | * @dev Handles hodler reward, TokenController should create and own it. 10 | */ 11 | contract Hodler is Ownable { 12 | using SafeMath for uint; 13 | 14 | // HODLER reward tracker 15 | // stake amount per address 16 | struct HODL { 17 | uint256 stake; 18 | // moving ANY funds invalidates hodling of the address 19 | bool invalid; 20 | bool claimed3M; 21 | bool claimed6M; 22 | bool claimed9M; 23 | } 24 | 25 | mapping (address => HODL) public hodlerStakes; 26 | 27 | // total current staking value and hodler addresses 28 | uint256 public hodlerTotalValue; 29 | uint256 public hodlerTotalCount; 30 | 31 | // store dates and total stake values for 3 - 6 - 9 months after normal sale 32 | uint256 public hodlerTotalValue3M; 33 | uint256 public hodlerTotalValue6M; 34 | uint256 public hodlerTotalValue9M; 35 | uint256 public hodlerTimeStart; 36 | uint256 public hodlerTime3M; 37 | uint256 public hodlerTime6M; 38 | uint256 public hodlerTime9M; 39 | 40 | // reward HEAL token amount 41 | uint256 public TOKEN_HODL_3M; 42 | uint256 public TOKEN_HODL_6M; 43 | uint256 public TOKEN_HODL_9M; 44 | 45 | // total amount of tokens claimed so far 46 | uint256 public claimedTokens; 47 | 48 | 49 | event LogHodlSetStake(address indexed _setter, address indexed _beneficiary, uint256 _value); 50 | event LogHodlClaimed(address indexed _setter, address indexed _beneficiary, uint256 _value); 51 | event LogHodlStartSet(address indexed _setter, uint256 _time); 52 | 53 | 54 | /// @dev Only before hodl is started 55 | modifier beforeHodlStart() { 56 | if (hodlerTimeStart == 0 || now <= hodlerTimeStart) 57 | _; 58 | } 59 | 60 | /// @dev Contructor, it should be created by a TokenController 61 | function Hodler(uint256 _stake3m, uint256 _stake6m, uint256 _stake9m) { 62 | TOKEN_HODL_3M = _stake3m; 63 | TOKEN_HODL_6M = _stake6m; 64 | TOKEN_HODL_9M = _stake9m; 65 | } 66 | 67 | /// @notice Adding hodler stake to an account 68 | /// @dev Only owner contract can call it and before hodling period starts 69 | /// @param _beneficiary Recepient address of hodler stake 70 | /// @param _stake Amount of additional hodler stake 71 | function addHodlerStake(address _beneficiary, uint256 _stake) public onlyOwner beforeHodlStart { 72 | // real change and valid _beneficiary is needed 73 | if (_stake == 0 || _beneficiary == address(0)) 74 | return; 75 | 76 | // add stake and maintain count 77 | if (hodlerStakes[_beneficiary].stake == 0) 78 | hodlerTotalCount = hodlerTotalCount.add(1); 79 | 80 | hodlerStakes[_beneficiary].stake = hodlerStakes[_beneficiary].stake.add(_stake); 81 | 82 | hodlerTotalValue = hodlerTotalValue.add(_stake); 83 | 84 | LogHodlSetStake(msg.sender, _beneficiary, hodlerStakes[_beneficiary].stake); 85 | } 86 | 87 | /// @notice Setting hodler stake of an account 88 | /// @dev Only owner contract can call it and before hodling period starts 89 | /// @param _beneficiary Recepient address of hodler stake 90 | /// @param _stake Amount to set the hodler stake 91 | function setHodlerStake(address _beneficiary, uint256 _stake) public onlyOwner beforeHodlStart { 92 | // real change and valid _beneficiary is needed 93 | if (hodlerStakes[_beneficiary].stake == _stake || _beneficiary == address(0)) 94 | return; 95 | 96 | // add stake and maintain count 97 | if (hodlerStakes[_beneficiary].stake == 0 && _stake > 0) { 98 | hodlerTotalCount = hodlerTotalCount.add(1); 99 | } else if (hodlerStakes[_beneficiary].stake > 0 && _stake == 0) { 100 | hodlerTotalCount = hodlerTotalCount.sub(1); 101 | } 102 | 103 | uint256 _diff = _stake > hodlerStakes[_beneficiary].stake ? _stake.sub(hodlerStakes[_beneficiary].stake) : hodlerStakes[_beneficiary].stake.sub(_stake); 104 | if (_stake > hodlerStakes[_beneficiary].stake) { 105 | hodlerTotalValue = hodlerTotalValue.add(_diff); 106 | } else { 107 | hodlerTotalValue = hodlerTotalValue.sub(_diff); 108 | } 109 | hodlerStakes[_beneficiary].stake = _stake; 110 | 111 | LogHodlSetStake(msg.sender, _beneficiary, _stake); 112 | } 113 | 114 | /// @notice Setting hodler start period. 115 | /// @param _time The time when hodler reward starts counting 116 | function setHodlerTime(uint256 _time) public onlyOwner beforeHodlStart { 117 | require(_time >= now); 118 | 119 | hodlerTimeStart = _time; 120 | hodlerTime3M = _time.add(90 days); 121 | hodlerTime6M = _time.add(180 days); 122 | hodlerTime9M = _time.add(270 days); 123 | 124 | LogHodlStartSet(msg.sender, _time); 125 | } 126 | 127 | /// @notice Invalidates hodler account 128 | /// @dev Gets called by EthealController#onTransfer before every transaction 129 | function invalidate(address _account) public onlyOwner { 130 | if (hodlerStakes[_account].stake > 0 && !hodlerStakes[_account].invalid) { 131 | hodlerStakes[_account].invalid = true; 132 | hodlerTotalValue = hodlerTotalValue.sub(hodlerStakes[_account].stake); 133 | hodlerTotalCount = hodlerTotalCount.sub(1); 134 | } 135 | 136 | // update hodl total values "automatically" - whenever someone sends funds thus 137 | updateAndGetHodlTotalValue(); 138 | } 139 | 140 | /// @notice Claiming HODL reward for msg.sender 141 | function claimHodlReward() public { 142 | claimHodlRewardFor(msg.sender); 143 | } 144 | 145 | /// @notice Claiming HODL reward for an address 146 | function claimHodlRewardFor(address _beneficiary) public { 147 | // only when the address has a valid stake 148 | require(hodlerStakes[_beneficiary].stake > 0 && !hodlerStakes[_beneficiary].invalid); 149 | 150 | uint256 _stake = 0; 151 | 152 | // update hodl total values 153 | updateAndGetHodlTotalValue(); 154 | 155 | // claim hodl if not claimed 156 | if (!hodlerStakes[_beneficiary].claimed3M && now >= hodlerTime3M) { 157 | _stake = _stake.add(hodlerStakes[_beneficiary].stake.mul(TOKEN_HODL_3M).div(hodlerTotalValue3M)); 158 | hodlerStakes[_beneficiary].claimed3M = true; 159 | } 160 | if (!hodlerStakes[_beneficiary].claimed6M && now >= hodlerTime6M) { 161 | _stake = _stake.add(hodlerStakes[_beneficiary].stake.mul(TOKEN_HODL_6M).div(hodlerTotalValue6M)); 162 | hodlerStakes[_beneficiary].claimed6M = true; 163 | } 164 | if (!hodlerStakes[_beneficiary].claimed9M && now >= hodlerTime9M) { 165 | _stake = _stake.add(hodlerStakes[_beneficiary].stake.mul(TOKEN_HODL_9M).div(hodlerTotalValue9M)); 166 | hodlerStakes[_beneficiary].claimed9M = true; 167 | } 168 | 169 | if (_stake > 0) { 170 | // increasing claimed tokens 171 | claimedTokens = claimedTokens.add(_stake); 172 | 173 | // transferring tokens 174 | require(TokenController(owner).ethealToken().transfer(_beneficiary, _stake)); 175 | 176 | // log 177 | LogHodlClaimed(msg.sender, _beneficiary, _stake); 178 | } 179 | } 180 | 181 | /// @notice claimHodlRewardFor() for multiple addresses 182 | /// @dev Anyone can call this function and distribute hodl rewards 183 | /// @param _beneficiaries Array of addresses for which we want to claim hodl rewards 184 | function claimHodlRewardsFor(address[] _beneficiaries) external { 185 | for (uint256 i = 0; i < _beneficiaries.length; i++) 186 | claimHodlRewardFor(_beneficiaries[i]); 187 | } 188 | 189 | /// @notice Setting 3 - 6 - 9 months total staking hodl value if time is come 190 | function updateAndGetHodlTotalValue() public returns (uint) { 191 | if (now >= hodlerTime3M && hodlerTotalValue3M == 0) { 192 | hodlerTotalValue3M = hodlerTotalValue; 193 | } 194 | 195 | if (now >= hodlerTime6M && hodlerTotalValue6M == 0) { 196 | hodlerTotalValue6M = hodlerTotalValue; 197 | } 198 | 199 | if (now >= hodlerTime9M && hodlerTotalValue9M == 0) { 200 | hodlerTotalValue9M = hodlerTotalValue; 201 | 202 | // since we can transfer more tokens to this contract, make it possible to retain more than the predefined limit 203 | TOKEN_HODL_9M = TokenController(owner).ethealToken().balanceOf(this).sub(TOKEN_HODL_3M).sub(TOKEN_HODL_6M).add(claimedTokens); 204 | } 205 | 206 | return hodlerTotalValue; 207 | } 208 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # etheal-sale 2 | Solidity contract for [etheal](https://etheal.com) token and sale rounds 3 | 4 | ## Contract Structure 5 | 6 | ### Unique contracts 7 | 1. EthealController: controlling the Etheal MiniMeToken 8 | 2. Hodler: managing the hodler reward fund 9 | 3. EthealPreSale: managing presale 10 | 4. EthealNormalSale: managing normal sale 11 | 5. EthealDeposit: handling deposit before being whitelisted, and saving date for sending data 12 | 6. EthealWhitelist: handling KYC 13 | 7. EthealPromoToken: promo token, which gives additional bonus during sale 14 | 15 | Please see detailed description at the bottom. 16 | 17 | ### Basic contracts 18 | * SafeMath: basic OpenZeppelin SafeMath for safe math functions 19 | * ECRecovery: basic OpenZeppelin ECRecovery contract for signature checking 20 | * Wallet: basic consensys multisig wallet 21 | * Ownable: basic OpenZeppelin Ownable contract 22 | * Pausable: basic OpenZeppelin Pausable contract 23 | 24 | ### MiniMe contracts 25 | * ERC20: basic ERC20 interface 26 | * ERC20MiniMe: is an ERC20 interface for MiniMe token 27 | * Controlled: basic Controlled contract needed for MiniMe 28 | * MiniMeToken: basic 0.2 version of MiniMe token 29 | * TokenController: token controller interface needed to controll the Etheal MiniMe token 30 | * EthealToken: is a very basic MiniMe token instanciation 31 | 32 | ### Crowdsale basic contracts 33 | * HasNoTokens: basic token to implement extraction of mistakenly sent tokens 34 | * Crowdsale: basic OpenZeppelin Crowdsale with 3 small modifications 35 | * ERC20 token replaced with ERC20MiniMe token 36 | * Distinct tokenTransfer function to make it extensible 37 | * hasStarted function to know whether the crowdsale is started 38 | * CappedCrowdsale: basic OpenZeppelin CappedCrowdsale contract 39 | * implementing partial refund (https://github.com/OpenZeppelin/zeppelin-solidity/pull/499) 40 | * FinalizableCrowdsale: basic OpenZeppelin FinalizableCrowdsale contract 41 | * RefundableCrowdsale: basic OpenZeppelin RefundableCrowdsale contract 42 | * with a modification to forward funds to multisig wallet after reaching the cap, thus securing the funds as soon as it makes sense 43 | * RefundVault: basic OpenZeppelin RefunVault contract 44 | * with extension of HasNoTokens, to recover mistakenly sent tokens 45 | * TokenVesting: basic OpenZeppelin TokenVesting contract 46 | 47 | ### EthealController 48 | Controlls the EthealToken contract, the initial HEAL token distribution, handles Grants (vesting tokens for team and advisors), hodler reward and crowdsale. 49 | 50 | It is a pausable contract, which is paused at initialization. While paused only this contract and crowdsale contracts can move funds of HEAL token. 51 | 52 | It implements HasNoTokens to recover mistakenly sent tokens to this contract. 53 | 54 | All the tokens it holds can be used to create and revoke grants, transfer tokens to existing but not started grants. 55 | 56 | Tokens for future crowdsales are held at the address of 0x1, which can be only moved to a crowdsale contract. Crowdsale contracts send excess HEAL tokens back to address 0x1. If there is no active crowdsale (which has started but not ended), then it can set a new crowdsale contract and transfer tokens to it. 57 | 58 | We have decided to handle crowdsales in separate contract to the EthealController, because there will be several rounds of sales, and the exact timing of round 2 and 3 is unknown yet. 59 | 60 | ![Token Distribution](https://etheal.com/img/chart-heal-token.svg "Token Distribution") 61 | 62 | Token distribution: 63 | * SALE address (0x1): 43M HEAL tokens for future sales rounds 64 | * HODLER reward contract: 10M HEAL tokens 65 | * Deployer of contracts: 3.5M HEAL tokens for referral + bounty tokens 66 | * excess tokens will be sent to the HODLER reward contract 67 | * Multisig Wallet: 20M HEAL tokens for Community Fund 68 | * EthealController: 20.5M HEAL tokens for team, founders, advisors 69 | * it can be only withdrawn through grants 70 | * team: 4 years vesting with one year cliff 71 | * advisors: 6 months vesting with three months cliff 72 | * 2 investor addresses: 3M HEAL tokens 73 | 74 | Only the multisig wallet can burn tokens of the EthealController (which belongs to the team and advisors), or burn not yet assigned crowdsale tokens. In the future the controller may be used to burn some of its own profit. 75 | 76 | Also the multisig wallet can replace the EthealController with a new one, which can be used for future updates. This transfers the controller rights of EthealToken and hodler reward contract to the new controller, and transfers all eth and HEAL tokens to the new controller. Previously issued and revoced grants will transfer excess HEAL tokens to the old controller, which can be retrieved after a newController is set. 77 | 78 | It also implements proxy functions to hodler reward, which enables crowdsale contracts to set hodler stakes. 79 | 80 | It implements proxy functions to EthealToken (MiniMe), which stops transfering HEAL tokens when EthealController is stopped, refuses ETH transfers to the EthealToken contract, invalidates hodler stakes whenever any amount of heal token is moved from an address, and helps to recover accidentally sent tokens (other than the EthealToken) to the EthealToken contract. 81 | 82 | 83 | ### Hodler 84 | Only crowdsale contracts can interract with it, and it accepts modifications until its start time. 85 | 86 | Implements hodler reward logic: 87 | Keep tokens intact (can’t move any portion of it) on your wallet for 3/6/9 months after two weeks of ending the normal sale, and 20M HEAL token HODLER reward will be distributed among presale and sale HODLERs in the ratio of their intact stakes to the total amount. 88 | 89 | * HODLER lot 3 months: 1,000,000 HEAL 90 | * HODLER lot 6 months: 2,000,000 HEAL 91 | * HODLER lot 9 months: 17,000,000 HEAL 92 | 93 | Moving any portion of HEAL tokens from an address invalidates its stakes within the hodler reward. 94 | 95 | Remaining HEAL tokens from Referral reward will be moved to hodler lot 9 months. 96 | 97 | 98 | ### EthealPreSale 99 | It is pausable, when paused no contribution is accepted. 100 | 101 | It is capped, reaching the cap stops the sale immediately. 102 | 103 | It is refundable, when not reaching the goal everyone gets back their full contribution in ETH and all the HEAL tokens is transferred back to the EthealController. 104 | 105 | It implements a softcap logic, which means after reaching the soft cap the crowdsale is closed in 120 hours. 106 | 107 | Sending funds below the minimum contribution amount (0.1ETH) is rejected. 108 | 109 | Sending funds above the maximum gas price (100gwei), calculates stakes on 80%. If you send 5eth with 101gwei gas price results in calculating your funds as 4eth. In case of not reaching minimum goal, 5eth is refunded. In case of reaching the goal you get 4eth * 1250 = 5000 HEAL tokens. 110 | 111 | It implements partial refunding for the last contributor, so the user don't have to be smart, the contract is smart instead. If there is only 1 eth remained, and the last contributor send 5 eth, then 4 eth is refunded. 112 | 113 | Before token sale start parameters can be changed: max gas price and penalty, minimum contribution, minimum goal and soft and hard caps, starting and end times, and rate. 114 | 115 | 116 | It implements **whitelist** logic as follows: 117 | * Whitelisted days can be defined with corresponding max stakes, and whitelisted addresses can contribute until they have stakes no bigger than the max stake defined for that day. 118 | * After whitelist period everyone can contribute until reaching the maximum cap. 119 | * It takes into account the max gas price penalty, eg: 120 | * if 10eth is the max stake for day 2 of whitelist, and you already have 6eth stakes 121 | * then either you can send 4eth with gas price less than or equal to 100gwei 122 | * or 5eth with more than 100gwei gas price, since then 5*80%=4eth stake will be credited to you 123 | * The smartcontract is ***smart***, so the user doesn't have to. Sending excess funds results in partial refund. 124 | * Eg. in the previous case if you send 10 eth with lower than 100gwei gas price results in crediting 4eth stake to you and refunding 6eth. 125 | 126 | 127 | ### EthealNormalSale 128 | $10M hard cap sale, with 700 HEAL / ETH base price, $4.8M soft cap. Can deposit earlier than start, but above a certain limit whitelisting is needed, either writing address to the EthealWhitelist contract, or offchain signing the address of the contributor. 129 | 130 | Time-based bonus structure: 131 | ![Normal Sale bonus](https://etheal.com/img/chart-token-sale-bonus.svg "Normal Sale bonus") 132 | 133 | Volume-based bonus: 134 | * >= 100eth: +4% 135 | * >= 10eth: +2% 136 | 137 | Sending some promo token to one of the following addresses, results in +5% token bonus: 138 | * 0x0000000000000000000000000000000000000001 139 | * EthealPromoTokenController 140 | * EthealNormalSale 141 | 142 | 143 | ## Deployment 144 | 145 | ### Initial deployment 146 | 1) deploy multisig wallet 147 | 2) deploy MiniMeTokenFactory 148 | 3) deploy EthealController 149 | 4) deploy EthealToken with EthealController address and MiniMeTokenFactory address 150 | 5) EthealController -> setEthealToken(EthealToken.address, 0) 151 | * 0 is for the Hodler reward contract address, it means the controller will create and assign to itself a new hodler contract 152 | 6) deploy EthealPromoTokenController 153 | 7) deploy EthealPromoToken with EthealPromoTokenController and MiniMeTokenFactory addresses 154 | 8) deploy EthealWhitelist with signer address 155 | 9) deploy EthealNormalSale 156 | 10) EthealController -> setCrowdsaleTransfer PreSale.address 157 | 11) EthealNormalSale 158 | * setPromoTokenController -> EthealPromoTokenController address 159 | * setWhitelist -> EthealWhitelist address and minimum threshold above which whitelisting is needed 160 | 12) EthealPromoTokenController 161 | * setCrowdsale -> EthealNormalSale address 162 | * setPromoToken -> EthealPromoToken address 163 | 13) deploy EthealDeposit 164 | 14) EthealNormalSale setDeposit -> EthealDeposit address 165 | 166 | ### Deploying a new crowdsale 167 | *only when no active crowdsale is present* 168 | 1) deploy Crowdsale with the address of EthealController 169 | 2) send funds and set address with EthealController.setCrowdsaleTransfer 170 | 171 | ### Deploying a new EthealToken fork 172 | 1) EthealToken -> createCloneToken 173 | 2) EthealController -> setEthealToken(new EthealToken.address, 0) 174 | 175 | ### Deploying a new EthealController 176 | *multisig wallet is needed* 177 | 1) deploy new EthealController 178 | 2) new EthealController -> setEthealToken(EthealToken.address, Hodler.address) 179 | 3) old EthealController -> setNewController(new EthealController.address) 180 | -------------------------------------------------------------------------------- /contracts/EthealDeposit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import './SafeMath.sol'; 4 | import './Ownable.sol'; 5 | import './HasNoTokens.sol'; 6 | import './EthealWhitelist.sol'; 7 | import './iEthealSale.sol'; 8 | 9 | /** 10 | * @title EthealDeposit 11 | * @author thesved 12 | * @dev This contract is used for storing funds while doing Whitelist 13 | */ 14 | contract EthealDeposit is Ownable, HasNoTokens { 15 | using SafeMath for uint256; 16 | 17 | // storing deposits: make sure they fit in 2 x 32 byte 18 | struct Deposit { 19 | uint256 amount; // 32 byte 20 | address beneficiary; // 20 byte 21 | uint64 time; // 8 byte 22 | bool cleared; // 1 bit 23 | } 24 | uint256 public transactionCount; 25 | uint256 public pendingCount; 26 | mapping (uint256 => Deposit) public transactions; // store transactions 27 | mapping (address => uint256[]) public addressTransactions; // store transaction ids for addresses 28 | 29 | // sale contract to which we forward funds 30 | iEthealSale public sale; 31 | EthealWhitelist public whitelist; 32 | 33 | event LogDeposited(address indexed beneficiary, uint256 weiAmount, uint256 id); 34 | event LogRefunded(address indexed beneficiary, uint256 weiAmount, uint256 id); 35 | event LogForwarded(address indexed beneficiary, uint256 weiAmount, uint256 id); 36 | 37 | //////////////// 38 | // Constructor 39 | //////////////// 40 | 41 | /// @notice Etheal deposit constructor 42 | /// @param _sale address of sale contract 43 | /// @param _whitelist address of whitelist contract 44 | function EthealDeposit(address _sale, address _whitelist) { 45 | require(_sale != address(0)); 46 | sale = iEthealSale(_sale); 47 | whitelist = EthealWhitelist(_whitelist); 48 | } 49 | 50 | /// @notice Set sale contract address 51 | function setSale(address _sale) public onlyOwner { 52 | sale = iEthealSale(_sale); 53 | } 54 | 55 | /// @notice Set whitelist contract address 56 | function setWhitelist(address _whitelist) public onlyOwner { 57 | whitelist = EthealWhitelist(_whitelist); 58 | } 59 | 60 | /// @dev Override HasNoTokens#extractTokens to not be able to extract tokens until saleEnd and everyone got their funds back 61 | function extractTokens(address _token, address _claimer) public onlyOwner saleEnded { 62 | require(pendingCount == 0); 63 | 64 | super.extractTokens(_token, _claimer); 65 | } 66 | 67 | 68 | //////////////// 69 | // Deposit, forward, refund 70 | //////////////// 71 | 72 | modifier whitelistSet() { 73 | require(address(whitelist) != address(0)); 74 | _; 75 | } 76 | 77 | modifier saleNotEnded() { 78 | require(address(sale) != address(0) && !sale.hasEnded()); 79 | _; 80 | } 81 | 82 | modifier saleNotPaused() { 83 | require(address(sale) != address(0) && !sale.paused()); 84 | _; 85 | } 86 | 87 | modifier saleEnded() { 88 | require(address(sale) != address(0) && sale.hasEnded()); 89 | _; 90 | } 91 | 92 | /// @notice payable fallback calls the deposit function 93 | function() public payable { 94 | deposit(msg.sender, ""); 95 | } 96 | 97 | /// @notice depositing for investor, return transaction Id 98 | /// @param _investor address of investor 99 | /// @param _whitelistSign offchain whitelist signiture for address, optional 100 | function deposit(address _investor, bytes _whitelistSign) public payable whitelistSet saleNotEnded returns (uint256) { 101 | require(_investor != address(0)); 102 | require(msg.value > 0); 103 | require(msg.value >= sale.minContribution()); 104 | 105 | uint256 transactionId = addTransaction(_investor, msg.value); 106 | 107 | // forward transaction automatically if whitelist is okay, so the transaction doesnt revert 108 | if (whitelist.isWhitelisted(_investor) 109 | || whitelist.isOffchainWhitelisted(_investor, _whitelistSign) 110 | || sale.whitelistThreshold() >= sale.stakes(_investor).add(msg.value) 111 | ) { 112 | // only forward if sale is not paused 113 | if (!sale.paused()) { 114 | forwardTransactionInternal(transactionId, _whitelistSign); 115 | } 116 | } 117 | 118 | return transactionId; 119 | } 120 | 121 | /// @notice forwarding a transaction 122 | function forwardTransaction(uint256 _id, bytes _whitelistSign) public whitelistSet saleNotEnded saleNotPaused { 123 | require(forwardTransactionInternal(_id, _whitelistSign)); 124 | } 125 | 126 | /// @notice forwarding multiple transactions: check whitelist 127 | function forwardManyTransaction(uint256[] _ids) public whitelistSet saleNotEnded saleNotPaused { 128 | uint256 _threshold = sale.whitelistThreshold(); 129 | 130 | for (uint256 i=0; i<_ids.length; i++) { 131 | // only forward if it is within threshold or whitelisted, so the transaction doesnt revert 132 | if ( whitelist.isWhitelisted(transactions[_ids[i]].beneficiary) 133 | || _threshold >= sale.stakes(transactions[_ids[i]].beneficiary).add(transactions[_ids[i]].amount ) 134 | ) { 135 | forwardTransactionInternal(_ids[i],""); 136 | } 137 | } 138 | } 139 | 140 | /// @notice forwarding transactions for an investor 141 | function forwardInvestorTransaction(address _investor, bytes _whitelistSign) public whitelistSet saleNotEnded saleNotPaused { 142 | bool _whitelisted = whitelist.isWhitelisted(_investor) || whitelist.isOffchainWhitelisted(_investor, _whitelistSign); 143 | uint256 _amount = sale.stakes(_investor); 144 | uint256 _threshold = sale.whitelistThreshold(); 145 | 146 | for (uint256 i=0; i= _amount) { 150 | forwardTransactionInternal(addressTransactions[_investor][i], _whitelistSign); 151 | } 152 | } 153 | } 154 | 155 | /// @notice refunding a transaction 156 | function refundTransaction(uint256 _id) public saleEnded { 157 | require(refundTransactionInternal(_id)); 158 | } 159 | 160 | /// @notice refunding multiple transactions 161 | function refundManyTransaction(uint256[] _ids) public saleEnded { 162 | for (uint256 i=0; i<_ids.length; i++) { 163 | refundTransactionInternal(_ids[i]); 164 | } 165 | } 166 | 167 | /// @notice refunding an investor 168 | function refundInvestor(address _investor) public saleEnded { 169 | for (uint256 i=0; i {data position}{data length}data 212 | bytes memory _whitelistCall = bytesToArgument(_whitelistSign, 96); 213 | 214 | // forwarding transaction to sale contract 215 | if (! sale.call.value(transactions[_id].amount)(bytes4(keccak256('depositEth(address,uint256,bytes)')), transactions[_id].beneficiary, uint256(transactions[_id].time), _whitelistCall) ) { 216 | return false; 217 | } 218 | transactions[_id].cleared = true; 219 | 220 | pendingCount = pendingCount.sub(1); 221 | LogForwarded(transactions[_id].beneficiary, transactions[_id].amount, _id); 222 | 223 | return true; 224 | } 225 | 226 | /// @dev Fixing low level call for providing signature information: create proper padding for bytes information 227 | function bytesToArgument(bytes memory _sign, uint256 _position) internal pure returns (bytes memory c) { 228 | uint256 signLength = _sign.length; 229 | uint256 totalLength = signLength.add(64); 230 | uint256 loopMax = signLength.add(31).div(32); 231 | assembly { 232 | let m := mload(0x40) 233 | mstore(m, totalLength) // store the total length 234 | mstore(add(m,32), _position) // where does the data start 235 | mstore(add(m,64), signLength) // store the length of signature 236 | for { let i := 0 } lt(i, loopMax) { i := add(1, i) } { mstore(add(m, mul(32, add(3, i))), mload(add(_sign, mul(32, add(1, i))))) } 237 | mstore(0x40, add(m, add(32, totalLength))) 238 | c := m 239 | } 240 | } 241 | 242 | /// @notice Send back non-cleared transactions after sale is over, not checking status for speeding up mass actions 243 | function refundTransactionInternal(uint256 _id) internal returns (bool) { 244 | require(_id < transactionCount); 245 | 246 | // if already cleared then return false 247 | if (transactions[_id].cleared) { 248 | return false; 249 | } 250 | 251 | // sending back funds 252 | transactions[_id].cleared = true; 253 | transactions[_id].beneficiary.transfer(transactions[_id].amount); 254 | 255 | pendingCount = pendingCount.sub(1); 256 | LogRefunded(transactions[_id].beneficiary, transactions[_id].amount, _id); 257 | 258 | return true; 259 | } 260 | 261 | 262 | //////////////// 263 | // External functions 264 | //////////////// 265 | 266 | /// @notice gives back transaction ids based on filtering 267 | function getTransactionIds(uint256 from, uint256 to, bool _cleared, bool _nonCleared) view external returns (uint256[] ids) { 268 | uint256 i = 0; 269 | uint256 results = 0; 270 | uint256[] memory _ids = new uint256[](transactionCount); 271 | 272 | // search in contributors 273 | for (i = 0; i < transactionCount; i++) { 274 | if (_cleared && transactions[i].cleared || _nonCleared && !transactions[i].cleared) { 275 | _ids[results] = i; 276 | results++; 277 | } 278 | } 279 | 280 | ids = new uint256[](results); 281 | for (i = from; i <= to && i < results; i++) { 282 | ids[i] = _ids[i]; 283 | } 284 | 285 | return ids; 286 | } 287 | } -------------------------------------------------------------------------------- /contracts/EthealController.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./SafeMath.sol"; 4 | import "./ERC20MiniMe.sol"; 5 | import "./Crowdsale.sol"; 6 | import "./TokenController.sol"; 7 | import "./Pausable.sol"; 8 | import "./Hodler.sol"; 9 | import "./TokenVesting.sol"; 10 | import "./HasNoTokens.sol"; 11 | 12 | 13 | /** 14 | * @title EthealController 15 | * @author thesved 16 | * @notice Controller of the Etheal Token 17 | * @dev Crowdsale can be only replaced when no active crowdsale is running. 18 | * The contract is paused by default. It has to be unpaused to enable token transfer. 19 | */ 20 | contract EthealController is Pausable, HasNoTokens, TokenController { 21 | using SafeMath for uint; 22 | 23 | // when migrating this contains the address of the new controller 24 | TokenController public newController; 25 | 26 | // token contract 27 | ERC20MiniMe public ethealToken; 28 | 29 | // distribution of tokens 30 | uint256 public constant ETHEAL_UNIT = 10**18; 31 | uint256 public constant THOUSAND = 10**3; 32 | uint256 public constant MILLION = 10**6; 33 | uint256 public constant TOKEN_SALE1_PRE = 9 * MILLION * ETHEAL_UNIT; 34 | uint256 public constant TOKEN_SALE1_NORMAL = 20 * MILLION * ETHEAL_UNIT; 35 | uint256 public constant TOKEN_SALE2 = 9 * MILLION * ETHEAL_UNIT; 36 | uint256 public constant TOKEN_SALE3 = 5 * MILLION * ETHEAL_UNIT; 37 | uint256 public constant TOKEN_HODL_3M = 1 * MILLION * ETHEAL_UNIT; 38 | uint256 public constant TOKEN_HODL_6M = 2 * MILLION * ETHEAL_UNIT; 39 | uint256 public constant TOKEN_HODL_9M = 7 * MILLION * ETHEAL_UNIT; 40 | uint256 public constant TOKEN_REFERRAL = 2 * MILLION * ETHEAL_UNIT; 41 | uint256 public constant TOKEN_BOUNTY = 1500 * THOUSAND * ETHEAL_UNIT; 42 | uint256 public constant TOKEN_COMMUNITY = 20 * MILLION * ETHEAL_UNIT; 43 | uint256 public constant TOKEN_TEAM = 14 * MILLION * ETHEAL_UNIT; 44 | uint256 public constant TOKEN_FOUNDERS = 6500 * THOUSAND * ETHEAL_UNIT; 45 | uint256 public constant TOKEN_INVESTORS = 3 * MILLION * ETHEAL_UNIT; 46 | 47 | // addresses only SALE will remain, the others will be real eth addresses 48 | address public SALE = 0X1; 49 | address public FOUNDER1 = 0X2; 50 | address public FOUNDER2 = 0X3; 51 | address public INVESTOR1 = 0X4; 52 | address public INVESTOR2 = 0X5; 53 | 54 | // addresses for multisig and crowdsale 55 | address public ethealMultisigWallet; 56 | Crowdsale public crowdsale; 57 | 58 | // hodler reward contract 59 | Hodler public hodlerReward; 60 | 61 | // token grants 62 | TokenVesting[] public tokenGrants; 63 | uint256 public constant VESTING_TEAM_CLIFF = 365 days; 64 | uint256 public constant VESTING_TEAM_DURATION = 4 * 365 days; 65 | uint256 public constant VESTING_ADVISOR_CLIFF = 3 * 30 days; 66 | uint256 public constant VESTING_ADVISOR_DURATION = 6 * 30 days; 67 | 68 | 69 | /// @dev only the crowdsale can call it 70 | modifier onlyCrowdsale() { 71 | require(msg.sender == address(crowdsale)); 72 | _; 73 | } 74 | 75 | /// @dev only the crowdsale can call it 76 | modifier onlyEthealMultisig() { 77 | require(msg.sender == address(ethealMultisigWallet)); 78 | _; 79 | } 80 | 81 | 82 | //////////////// 83 | // Constructor, overrides 84 | //////////////// 85 | 86 | /// @notice Constructor for Etheal Controller 87 | function EthealController(address _wallet) { 88 | require(_wallet != address(0)); 89 | 90 | paused = true; 91 | ethealMultisigWallet = _wallet; 92 | } 93 | 94 | /// @dev overrides HasNoTokens#extractTokens to make it possible to extract any tokens after migration or before that any tokens except etheal 95 | function extractTokens(address _token, address _claimer) onlyOwner public { 96 | require(newController != address(0) || _token != address(ethealToken)); 97 | 98 | super.extractTokens(_token, _claimer); 99 | } 100 | 101 | 102 | //////////////// 103 | // Manage crowdsale 104 | //////////////// 105 | 106 | /// @notice Set crowdsale address and transfer HEAL tokens from ethealController's SALE address 107 | /// @dev Crowdsale can be only set when the current crowdsale is not active and ethealToken is set 108 | function setCrowdsaleTransfer(address _sale, uint256 _amount) public onlyOwner { 109 | require (_sale != address(0) && !isCrowdsaleOpen() && address(ethealToken) != address(0)); 110 | 111 | crowdsale = Crowdsale(_sale); 112 | 113 | // transfer HEAL tokens to crowdsale account from the account of controller 114 | require(ethealToken.transferFrom(SALE, _sale, _amount)); 115 | } 116 | 117 | /// @notice Is there a not ended crowdsale? 118 | /// @return true if there is no crowdsale or the current crowdsale is not yet ended but started 119 | function isCrowdsaleOpen() public view returns (bool) { 120 | return address(crowdsale) != address(0) && !crowdsale.hasEnded() && crowdsale.hasStarted(); 121 | } 122 | 123 | 124 | //////////////// 125 | // Manage grants 126 | //////////////// 127 | 128 | /// @notice Grant vesting token to an address 129 | function createGrant(address _beneficiary, uint256 _amount, bool _revocable, bool _advisor) public onlyOwner { 130 | require(_beneficiary != address(0) && _amount > 0); 131 | 132 | // create token grant 133 | if (_advisor) { 134 | tokenGrants.push(new TokenVesting(_beneficiary, now, VESTING_ADVISOR_CLIFF, VESTING_ADVISOR_DURATION, _revocable)); 135 | } else { 136 | tokenGrants.push(new TokenVesting(_beneficiary, now, VESTING_TEAM_CLIFF, VESTING_TEAM_DURATION, _revocable)); 137 | } 138 | 139 | // transfer funds to the grant 140 | require(ethealToken.transfer(address(tokenGrants[tokenGrants.length.sub(1)]), _amount)); 141 | } 142 | 143 | /// @notice Transfer tokens to a grant until it is starting 144 | function transferToGrant(uint256 _id, uint256 _amount) public onlyOwner { 145 | require(_id < tokenGrants.length && _amount > 0 && now < tokenGrants[_id].start()); 146 | 147 | // transfer funds to the grant 148 | require(ethealToken.transfer(address(tokenGrants[_id]), _amount)); 149 | } 150 | 151 | /// @dev Revoking grant 152 | function revokeGrant(uint256 _id) public onlyOwner { 153 | require(_id < tokenGrants.length); 154 | 155 | tokenGrants[_id].revoke(ethealToken); 156 | } 157 | 158 | /// @notice Returns the token grant count 159 | function getGrantCount() view public returns (uint) { 160 | return tokenGrants.length; 161 | } 162 | 163 | 164 | //////////////// 165 | // BURN, handle ownership - only multsig can call these functions! 166 | //////////////// 167 | 168 | /// @notice contract can burn its own or its sale tokens 169 | function burn(address _where, uint256 _amount) public onlyEthealMultisig { 170 | require(_where == address(this) || _where == SALE); 171 | 172 | require(ethealToken.destroyTokens(_where, _amount)); 173 | } 174 | 175 | /// @notice replaces controller when it was not yet replaced, only multisig can do it 176 | function setNewController(address _controller) public onlyEthealMultisig { 177 | require(_controller != address(0) && newController == address(0)); 178 | 179 | newController = TokenController(_controller); 180 | ethealToken.changeController(_controller); 181 | hodlerReward.transferOwnership(_controller); 182 | 183 | // send eth 184 | uint256 _stake = this.balance; 185 | if (_stake > 0) 186 | _controller.transfer(_stake); 187 | 188 | // send tokens 189 | _stake = ethealToken.balanceOf(this); 190 | if (_stake > 0) 191 | ethealToken.transfer(_controller, _stake); 192 | } 193 | 194 | /// @notice Set new multisig wallet, to make it upgradable. 195 | function setNewMultisig(address _wallet) public onlyEthealMultisig { 196 | require(_wallet != address(0)); 197 | 198 | ethealMultisigWallet = _wallet; 199 | } 200 | 201 | 202 | //////////////// 203 | // When PAUSED 204 | //////////////// 205 | 206 | /// @notice set the token, if no hodler provided then creates a hodler reward contract 207 | function setEthealToken(address _token, address _hodler) public onlyOwner whenPaused { 208 | require(_token != address(0)); 209 | 210 | ethealToken = ERC20MiniMe(_token); 211 | 212 | 213 | if (_hodler != address(0)) { 214 | // set hodler reward contract if provided 215 | hodlerReward = Hodler(_hodler); 216 | } else if (hodlerReward == address(0)) { 217 | // create hodler reward contract if not yet created 218 | hodlerReward = new Hodler(TOKEN_HODL_3M, TOKEN_HODL_6M, TOKEN_HODL_9M); 219 | } 220 | 221 | // MINT tokens if not minted yet 222 | if (ethealToken.totalSupply() == 0) { 223 | // sale 224 | ethealToken.generateTokens(SALE, TOKEN_SALE1_PRE.add(TOKEN_SALE1_NORMAL).add(TOKEN_SALE2).add(TOKEN_SALE3)); 225 | // hodler reward 226 | ethealToken.generateTokens(address(hodlerReward), TOKEN_HODL_3M.add(TOKEN_HODL_6M).add(TOKEN_HODL_9M)); 227 | // bounty + referral 228 | ethealToken.generateTokens(owner, TOKEN_BOUNTY.add(TOKEN_REFERRAL)); 229 | // community fund 230 | ethealToken.generateTokens(address(ethealMultisigWallet), TOKEN_COMMUNITY); 231 | // team -> grantable 232 | ethealToken.generateTokens(address(this), TOKEN_FOUNDERS.add(TOKEN_TEAM)); 233 | // investors 234 | ethealToken.generateTokens(INVESTOR1, TOKEN_INVESTORS.div(3).mul(2)); 235 | ethealToken.generateTokens(INVESTOR2, TOKEN_INVESTORS.div(3)); 236 | } 237 | } 238 | 239 | 240 | //////////////// 241 | // Proxy for Hodler contract 242 | //////////////// 243 | 244 | /// @notice Proxy call for setting hodler start time 245 | function setHodlerTime(uint256 _time) public onlyCrowdsale { 246 | hodlerReward.setHodlerTime(_time); 247 | } 248 | 249 | /// @notice Proxy call for adding hodler stake 250 | function addHodlerStake(address _beneficiary, uint256 _stake) public onlyCrowdsale { 251 | hodlerReward.addHodlerStake(_beneficiary, _stake); 252 | } 253 | 254 | /// @notice Proxy call for setting hodler stake 255 | function setHodlerStake(address _beneficiary, uint256 _stake) public onlyCrowdsale { 256 | hodlerReward.setHodlerStake(_beneficiary, _stake); 257 | } 258 | 259 | 260 | //////////////// 261 | // MiniMe Controller functions 262 | //////////////// 263 | 264 | /// @notice No eth payment to the token contract 265 | function proxyPayment(address _owner) payable public returns (bool) { 266 | revert(); 267 | } 268 | 269 | /// @notice Before transfers are enabled for everyone, only this and the crowdsale contract is allowed to distribute HEAL 270 | function onTransfer(address _from, address _to, uint256 _amount) public returns (bool) { 271 | // moving any funds makes hodl participation invalid 272 | hodlerReward.invalidate(_from); 273 | 274 | return !paused || _from == address(this) || _to == address(this) || _from == address(crowdsale) || _to == address(crowdsale); 275 | } 276 | 277 | function onApprove(address _owner, address _spender, uint256 _amount) public returns (bool) { 278 | return !paused; 279 | } 280 | 281 | /// @notice Retrieve mistakenly sent tokens (other than the etheal token) from the token contract 282 | function claimTokenTokens(address _token) public onlyOwner { 283 | require(_token != address(ethealToken)); 284 | 285 | ethealToken.claimTokens(_token); 286 | } 287 | } -------------------------------------------------------------------------------- /contracts/AbstractVirtualToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.16; 2 | 3 | import "./SafeMath.sol"; 4 | 5 | /* 6 | * ERC-20 Standard Token Smart Contract Interface. 7 | * Copyright © 2016–2017 by ABDK Consulting. 8 | * Author: Mikhail Vladimirov 9 | */ 10 | 11 | /** 12 | * ERC-20 standard token interface, as defined 13 | * here. 14 | */ 15 | contract Token { 16 | /** 17 | * Get total number of tokens in circulation. 18 | * 19 | * @return total number of tokens in circulation 20 | */ 21 | function totalSupply () view returns (uint256 supply); 22 | 23 | /** 24 | * Get number of tokens currently belonging to given owner. 25 | * 26 | * @param _owner address to get number of tokens currently belonging to the 27 | * owner of 28 | * @return number of tokens currently belonging to the owner of given address 29 | */ 30 | function balanceOf (address _owner) view returns (uint256 balance); 31 | 32 | /** 33 | * Transfer given number of tokens from message sender to given recipient. 34 | * 35 | * @param _to address to transfer tokens to the owner of 36 | * @param _value number of tokens to transfer to the owner of given address 37 | * @return true if tokens were transferred successfully, false otherwise 38 | */ 39 | function transfer (address _to, uint256 _value) returns (bool success); 40 | 41 | /** 42 | * Transfer given number of tokens from given owner to given recipient. 43 | * 44 | * @param _from address to transfer tokens from the owner of 45 | * @param _to address to transfer tokens to the owner of 46 | * @param _value number of tokens to transfer from given owner to given 47 | * recipient 48 | * @return true if tokens were transferred successfully, false otherwise 49 | */ 50 | function transferFrom (address _from, address _to, uint256 _value) returns (bool success); 51 | 52 | /** 53 | * Allow given spender to transfer given number of tokens from message sender. 54 | * 55 | * @param _spender address to allow the owner of to transfer tokens from 56 | * message sender 57 | * @param _value number of tokens to allow to transfer 58 | * @return true if token transfer was successfully approved, false otherwise 59 | */ 60 | function approve (address _spender, uint256 _value) returns (bool success); 61 | 62 | /** 63 | * Tell how many tokens given spender is currently allowed to transfer from 64 | * given owner. 65 | * 66 | * @param _owner address to get number of tokens allowed to be transferred 67 | * from the owner of 68 | * @param _spender address to get number of tokens allowed to be transferred 69 | * by the owner of 70 | * @return number of tokens given spender is currently allowed to transfer 71 | * from given owner 72 | */ 73 | function allowance (address _owner, address _spender) view returns (uint256 remaining); 74 | 75 | /** 76 | * Logged when tokens were transferred from one owner to another. 77 | * 78 | * @param _from address of the owner, tokens were transferred from 79 | * @param _to address of the owner, tokens were transferred to 80 | * @param _value number of tokens transferred 81 | */ 82 | event Transfer (address indexed _from, address indexed _to, uint256 _value); 83 | 84 | /** 85 | * Logged when owner approved his tokens to be transferred by some spender. 86 | * 87 | * @param _owner owner who approved his tokens to be transferred 88 | * @param _spender spender who were allowed to transfer the tokens belonging 89 | * to the owner 90 | * @param _value number of tokens belonging to the owner, approved to be 91 | * transferred by the spender 92 | */ 93 | event Approval (address indexed _owner, address indexed _spender, uint256 _value); 94 | } 95 | 96 | /* 97 | * Abstract Token Smart Contract. Copyright © 2017 by ABDK Consulting. 98 | * Author: Mikhail Vladimirov 99 | * Modified to use SafeMath library by thesved 100 | */ 101 | /** 102 | * Abstract Token Smart Contract that could be used as a base contract for 103 | * ERC-20 token contracts. 104 | */ 105 | contract AbstractToken is Token { 106 | using SafeMath for uint; 107 | 108 | /** 109 | * Create new Abstract Token contract. 110 | */ 111 | function AbstractToken () { 112 | // Do nothing 113 | } 114 | 115 | /** 116 | * Get number of tokens currently belonging to given owner. 117 | * 118 | * @param _owner address to get number of tokens currently belonging to the owner 119 | * @return number of tokens currently belonging to the owner of given address 120 | */ 121 | function balanceOf (address _owner) view returns (uint256 balance) { 122 | return accounts[_owner]; 123 | } 124 | 125 | /** 126 | * Transfer given number of tokens from message sender to given recipient. 127 | * 128 | * @param _to address to transfer tokens to the owner of 129 | * @param _value number of tokens to transfer to the owner of given address 130 | * @return true if tokens were transferred successfully, false otherwise 131 | */ 132 | function transfer (address _to, uint256 _value) returns (bool success) { 133 | uint256 fromBalance = accounts[msg.sender]; 134 | if (fromBalance < _value) return false; 135 | if (_value > 0 && msg.sender != _to) { 136 | accounts[msg.sender] = fromBalance.sub(_value); 137 | accounts[_to] = accounts[_to].add(_value); 138 | Transfer(msg.sender, _to, _value); 139 | } 140 | return true; 141 | } 142 | 143 | /** 144 | * Transfer given number of tokens from given owner to given recipient. 145 | * 146 | * @param _from address to transfer tokens from the owner of 147 | * @param _to address to transfer tokens to the owner of 148 | * @param _value number of tokens to transfer from given owner to given recipient 149 | * @return true if tokens were transferred successfully, false otherwise 150 | */ 151 | function transferFrom (address _from, address _to, uint256 _value) returns (bool success) { 152 | uint256 spenderAllowance = allowances[_from][msg.sender]; 153 | if (spenderAllowance < _value) return false; 154 | uint256 fromBalance = accounts[_from]; 155 | if (fromBalance < _value) return false; 156 | 157 | allowances[_from][msg.sender] = spenderAllowance.sub(_value); 158 | 159 | if (_value > 0 && _from != _to) { 160 | accounts[_from] = fromBalance.sub(_value); 161 | accounts[_to] = accounts[_to].add(_value); 162 | Transfer(_from, _to, _value); 163 | } 164 | return true; 165 | } 166 | 167 | /** 168 | * Allow given spender to transfer given number of tokens from message sender. 169 | * 170 | * @param _spender address to allow the owner of to transfer tokens from 171 | * message sender 172 | * @param _value number of tokens to allow to transfer 173 | * @return true if token transfer was successfully approved, false otherwise 174 | */ 175 | function approve (address _spender, uint256 _value) returns (bool success) { 176 | allowances[msg.sender][_spender] = _value; 177 | Approval(msg.sender, _spender, _value); 178 | 179 | return true; 180 | } 181 | 182 | /** 183 | * Tell how many tokens given spender is currently allowed to transfer from 184 | * given owner. 185 | * 186 | * @param _owner address to get number of tokens allowed to be transferred from the owner 187 | * @param _spender address to get number of tokens allowed to be transferred by the owner 188 | * @return number of tokens given spender is currently allowed to transfer from given owner 189 | */ 190 | function allowance (address _owner, address _spender) view returns (uint256 remaining) { 191 | return allowances[_owner][_spender]; 192 | } 193 | 194 | /** 195 | * Mapping from addresses of token holders to the numbers of tokens belonging 196 | * to these token holders. 197 | */ 198 | mapping (address => uint256) accounts; 199 | 200 | /** 201 | * Mapping from addresses of token holders to the mapping of addresses of 202 | * spenders to the allowances set by these token holders to these spenders. 203 | */ 204 | mapping (address => mapping (address => uint256)) private allowances; 205 | } 206 | 207 | 208 | /* 209 | * Abstract Virtual Token Smart Contract. Copyright © 2017 by ABDK Consulting. 210 | * Author: Mikhail Vladimirov 211 | * Modified to use SafeMath library by thesved 212 | */ 213 | 214 | /** 215 | * Abstract Token Smart Contract that could be used as a base contract for 216 | * ERC-20 token contracts supporting virtual balance. 217 | */ 218 | contract AbstractVirtualToken is AbstractToken { 219 | using SafeMath for uint; 220 | 221 | /** 222 | * Maximum number of real (i.e. non-virtual) tokens in circulation (2^255-1). 223 | */ 224 | uint256 constant MAXIMUM_TOKENS_COUNT = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 225 | 226 | /** 227 | * Mask used to extract real balance of an account (2^255-1). 228 | */ 229 | uint256 constant BALANCE_MASK = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 230 | 231 | /** 232 | * Mask used to extract "materialized" flag of an account (2^255). 233 | */ 234 | uint256 constant MATERIALIZED_FLAG_MASK = 0x8000000000000000000000000000000000000000000000000000000000000000; 235 | 236 | /** 237 | * Create new Abstract Virtual Token contract. 238 | */ 239 | function AbstractVirtualToken () { 240 | // Do nothing 241 | } 242 | 243 | /** 244 | * Get total number of tokens in circulation. 245 | * 246 | * @return total number of tokens in circulation 247 | */ 248 | function totalSupply () view returns (uint256 supply) { 249 | return tokensCount; 250 | } 251 | 252 | /** 253 | * Get number of tokens currently belonging to given owner. 254 | * 255 | * @param _owner address to get number of tokens currently belonging to the owner 256 | * @return number of tokens currently belonging to the owner of given address 257 | */ 258 | function balanceOf (address _owner) constant returns (uint256 balance) { 259 | return (accounts[_owner] & BALANCE_MASK).add(getVirtualBalance(_owner)); 260 | } 261 | 262 | /** 263 | * Transfer given number of tokens from message sender to given recipient. 264 | * 265 | * @param _to address to transfer tokens to the owner of 266 | * @param _value number of tokens to transfer to the owner of given address 267 | * @return true if tokens were transferred successfully, false otherwise 268 | */ 269 | function transfer (address _to, uint256 _value) returns (bool success) { 270 | if (_value > balanceOf(msg.sender)) return false; 271 | else { 272 | materializeBalanceIfNeeded(msg.sender, _value); 273 | return AbstractToken.transfer(_to, _value); 274 | } 275 | } 276 | 277 | /** 278 | * Transfer given number of tokens from given owner to given recipient. 279 | * 280 | * @param _from address to transfer tokens from the owner of 281 | * @param _to address to transfer tokens to the owner of 282 | * @param _value number of tokens to transfer from given owner to given 283 | * recipient 284 | * @return true if tokens were transferred successfully, false otherwise 285 | */ 286 | function transferFrom (address _from, address _to, uint256 _value) returns (bool success) { 287 | if (_value > allowance(_from, msg.sender)) return false; 288 | if (_value > balanceOf(_from)) return false; 289 | else { 290 | materializeBalanceIfNeeded(_from, _value); 291 | return AbstractToken.transferFrom(_from, _to, _value); 292 | } 293 | } 294 | 295 | /** 296 | * Get virtual balance of the owner of given address. 297 | * 298 | * @param _owner address to get virtual balance for the owner of 299 | * @return virtual balance of the owner of given address 300 | */ 301 | function virtualBalanceOf (address _owner) internal view returns (uint256 _virtualBalance); 302 | 303 | /** 304 | * Calculate virtual balance of the owner of given address taking into account 305 | * materialized flag and total number of real tokens already in circulation. 306 | */ 307 | function getVirtualBalance (address _owner) private view returns (uint256 _virtualBalance) { 308 | if (accounts [_owner] & MATERIALIZED_FLAG_MASK != 0) return 0; 309 | else { 310 | _virtualBalance = virtualBalanceOf(_owner); 311 | uint256 maxVirtualBalance = MAXIMUM_TOKENS_COUNT.sub(tokensCount); 312 | if (_virtualBalance > maxVirtualBalance) 313 | _virtualBalance = maxVirtualBalance; 314 | } 315 | } 316 | 317 | /** 318 | * Materialize virtual balance of the owner of given address if this will help 319 | * to transfer given number of tokens from it. 320 | * 321 | * @param _owner address to materialize virtual balance of 322 | * @param _value number of tokens to be transferred 323 | */ 324 | function materializeBalanceIfNeeded (address _owner, uint256 _value) private { 325 | uint256 storedBalance = accounts[_owner]; 326 | if (storedBalance & MATERIALIZED_FLAG_MASK == 0) { 327 | // Virtual balance is not materialized yet 328 | if (_value > storedBalance) { 329 | // Real balance is not enough 330 | uint256 virtualBalance = getVirtualBalance(_owner); 331 | require (_value.sub(storedBalance) <= virtualBalance); 332 | accounts[_owner] = MATERIALIZED_FLAG_MASK | storedBalance.add(virtualBalance); 333 | tokensCount = tokensCount.add(virtualBalance); 334 | } 335 | } 336 | } 337 | 338 | /** 339 | * Number of real (i.e. non-virtual) tokens in circulation. 340 | */ 341 | uint256 tokensCount; 342 | } -------------------------------------------------------------------------------- /contracts/Wallet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | 4 | /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. 5 | /// @author Stefan George - 6 | contract MultiSigWallet { 7 | uint constant public MAX_OWNER_COUNT = 50; 8 | 9 | event Confirmation(address indexed sender, uint indexed transactionId); 10 | event Revocation(address indexed sender, uint indexed transactionId); 11 | event Submission(uint indexed transactionId); 12 | event Execution(uint indexed transactionId); 13 | event ExecutionFailure(uint indexed transactionId); 14 | event Deposit(address indexed sender, uint value); 15 | event OwnerAddition(address indexed owner); 16 | event OwnerRemoval(address indexed owner); 17 | event RequirementChange(uint required); 18 | 19 | mapping (uint => Transaction) public transactions; 20 | mapping (uint => mapping (address => bool)) public confirmations; 21 | mapping (address => bool) public isOwner; 22 | address[] public owners; 23 | uint public required; 24 | uint public transactionCount; 25 | 26 | struct Transaction { 27 | address destination; 28 | uint value; 29 | bytes data; 30 | bool executed; 31 | } 32 | 33 | modifier onlyWallet() { 34 | if (msg.sender != address(this)) 35 | throw; 36 | _; 37 | } 38 | 39 | modifier ownerDoesNotExist(address owner) { 40 | if (isOwner[owner]) 41 | throw; 42 | _; 43 | } 44 | 45 | modifier ownerExists(address owner) { 46 | if (!isOwner[owner]) 47 | throw; 48 | _; 49 | } 50 | 51 | modifier transactionExists(uint transactionId) { 52 | if (transactions[transactionId].destination == 0) 53 | throw; 54 | _; 55 | } 56 | 57 | modifier confirmed(uint transactionId, address owner) { 58 | if (!confirmations[transactionId][owner]) 59 | throw; 60 | _; 61 | } 62 | 63 | modifier notConfirmed(uint transactionId, address owner) { 64 | if (confirmations[transactionId][owner]) 65 | throw; 66 | _; 67 | } 68 | 69 | modifier notExecuted(uint transactionId) { 70 | if (transactions[transactionId].executed) 71 | throw; 72 | _; 73 | } 74 | 75 | modifier notNull(address _address) { 76 | if (_address == 0) 77 | throw; 78 | _; 79 | } 80 | 81 | modifier validRequirement(uint ownerCount, uint _required) { 82 | if ( ownerCount > MAX_OWNER_COUNT 83 | || _required > ownerCount 84 | || _required == 0 85 | || ownerCount == 0) 86 | throw; 87 | _; 88 | } 89 | 90 | /// @dev Fallback function allows to deposit ether. 91 | function() 92 | payable 93 | { 94 | if (msg.value > 0) 95 | Deposit(msg.sender, msg.value); 96 | } 97 | 98 | /* 99 | * Public functions 100 | */ 101 | /// @dev Contract constructor sets initial owners and required number of confirmations. 102 | /// @param _owners List of initial owners. 103 | /// @param _required Number of required confirmations. 104 | function MultiSigWallet(address[] _owners, uint _required) 105 | public 106 | validRequirement(_owners.length, _required) 107 | { 108 | for (uint i=0; i<_owners.length; i++) { 109 | if (isOwner[_owners[i]] || _owners[i] == 0) 110 | throw; 111 | isOwner[_owners[i]] = true; 112 | } 113 | owners = _owners; 114 | required = _required; 115 | } 116 | 117 | /// @dev Allows to add a new owner. Transaction has to be sent by wallet. 118 | /// @param owner Address of new owner. 119 | function addOwner(address owner) 120 | public 121 | onlyWallet 122 | ownerDoesNotExist(owner) 123 | notNull(owner) 124 | validRequirement(owners.length + 1, required) 125 | { 126 | isOwner[owner] = true; 127 | owners.push(owner); 128 | OwnerAddition(owner); 129 | } 130 | 131 | /// @dev Allows to remove an owner. Transaction has to be sent by wallet. 132 | /// @param owner Address of owner. 133 | function removeOwner(address owner) 134 | public 135 | onlyWallet 136 | ownerExists(owner) 137 | { 138 | isOwner[owner] = false; 139 | for (uint i=0; i owners.length) 146 | changeRequirement(owners.length); 147 | OwnerRemoval(owner); 148 | } 149 | 150 | /// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet. 151 | /// @param owner Address of owner to be replaced. 152 | /// @param owner Address of new owner. 153 | function replaceOwner(address owner, address newOwner) 154 | public 155 | onlyWallet 156 | ownerExists(owner) 157 | ownerDoesNotExist(newOwner) 158 | { 159 | for (uint i=0; i 370 | contract MultiSigWalletWithDailyLimit is MultiSigWallet { 371 | 372 | event DailyLimitChange(uint dailyLimit); 373 | 374 | uint public dailyLimit; 375 | uint public lastDay; 376 | uint public spentToday; 377 | 378 | /* 379 | * Public functions 380 | */ 381 | /// @dev Contract constructor sets initial owners, required number of confirmations and daily withdraw limit. 382 | /// @param _owners List of initial owners. 383 | /// @param _required Number of required confirmations. 384 | /// @param _dailyLimit Amount in wei, which can be withdrawn without confirmations on a daily basis. 385 | function MultiSigWalletWithDailyLimit(address[] _owners, uint _required, uint _dailyLimit) 386 | public 387 | MultiSigWallet(_owners, _required) 388 | { 389 | dailyLimit = _dailyLimit; 390 | } 391 | 392 | /// @dev Allows to change the daily limit. Transaction has to be sent by wallet. 393 | /// @param _dailyLimit Amount in wei. 394 | function changeDailyLimit(uint _dailyLimit) 395 | public 396 | onlyWallet 397 | { 398 | dailyLimit = _dailyLimit; 399 | DailyLimitChange(_dailyLimit); 400 | } 401 | 402 | /// @dev Allows anyone to execute a confirmed transaction or ether withdraws until daily limit is reached. 403 | /// @param transactionId Transaction ID. 404 | function executeTransaction(uint transactionId) 405 | public 406 | notExecuted(transactionId) 407 | { 408 | Transaction tx = transactions[transactionId]; 409 | bool confirmed = isConfirmed(transactionId); 410 | if (confirmed || tx.data.length == 0 && isUnderLimit(tx.value)) { 411 | tx.executed = true; 412 | if (!confirmed) 413 | spentToday += tx.value; 414 | if (tx.destination.call.value(tx.value)(tx.data)) 415 | Execution(transactionId); 416 | else { 417 | ExecutionFailure(transactionId); 418 | tx.executed = false; 419 | if (!confirmed) 420 | spentToday -= tx.value; 421 | } 422 | } 423 | } 424 | 425 | /* 426 | * Internal functions 427 | */ 428 | /// @dev Returns if amount is within daily limit and resets spentToday after one day. 429 | /// @param amount Amount to withdraw. 430 | /// @return Returns if amount is under daily limit. 431 | function isUnderLimit(uint amount) 432 | internal 433 | returns (bool) 434 | { 435 | if (now > lastDay + 24 hours) { 436 | lastDay = now; 437 | spentToday = 0; 438 | } 439 | if (spentToday + amount > dailyLimit || spentToday + amount < spentToday) 440 | return false; 441 | return true; 442 | } 443 | 444 | /* 445 | * Web3 call functions 446 | */ 447 | /// @dev Returns maximum withdraw amount. 448 | /// @return Returns amount. 449 | function calcMaxWithdraw() 450 | public 451 | constant 452 | returns (uint) 453 | { 454 | if (now > lastDay + 24 hours) 455 | return dailyLimit; 456 | if (dailyLimit < spentToday) 457 | return 0; 458 | return dailyLimit - spentToday; 459 | } 460 | } -------------------------------------------------------------------------------- /test/Controller.js: -------------------------------------------------------------------------------- 1 | import ether from './helpers/ether' 2 | import gwei from './helpers/gwei' 3 | import {advanceBlock} from './helpers/advanceToBlock' 4 | import {increaseTimeTo, duration} from './helpers/increaseTime' 5 | import latestTime from './helpers/latestTime' 6 | import EVMThrow from './helpers/EVMThrow' 7 | 8 | const BigNumber = web3.BigNumber 9 | 10 | const should = require('chai') 11 | .use(require('chai-as-promised')) 12 | .use(require('chai-bignumber')(BigNumber)) 13 | .should() 14 | 15 | const Factory = artifacts.require('MiniMeTokenFactory') 16 | const Controller = artifacts.require('EthealController') 17 | const Token = artifacts.require('EthealToken') 18 | const Grant = artifacts.require('TokenVesting') 19 | const Crowdsale = artifacts.require('EthealNormalSale') 20 | const Hodler = artifacts.require('Hodler') 21 | 22 | contract('Controller', function ([deployer, investor, wallet, advisor, purchaser, teammate]) { 23 | 24 | const rate = new BigNumber(1000) 25 | const bonuses = [new BigNumber(1.4), new BigNumber(1.2), new BigNumber(1.15), new BigNumber(1.1), new BigNumber(1.05)] 26 | 27 | const cap = ether(10) 28 | const softCap = ether(5) 29 | const softCapTime = duration.hours(120) 30 | const lessThanCap = ether(8) 31 | const lessThanSoftCap = ether(4) 32 | 33 | const minContribution = ether(0.1) 34 | const maxGasPrice = gwei(100) 35 | const aboveGasLimit = maxGasPrice.plus(1) 36 | const maxGasPenalty = new BigNumber(80) 37 | const maxGasFix = maxGasPenalty.div(100) 38 | 39 | const expectedTokenAmount = rate.mul(cap) 40 | 41 | before(async function() { 42 | //Advance to the next block to correctly read time in the solidity "now" function interpreted by testrpc 43 | await advanceBlock() 44 | }) 45 | 46 | beforeEach(async function () { 47 | this.startTime = latestTime() + duration.weeks(1); 48 | this.endTime = this.startTime + duration.weeks(4); 49 | this.afterEndTime = this.endTime + duration.seconds(1) 50 | 51 | this.factory = await Factory.new(); 52 | this.controller = await Controller.new(wallet) 53 | this.token = await Token.new(this.controller.address, this.factory.address) 54 | await this.controller.setEthealToken(this.token.address, 0) 55 | this.hodler = Hodler.at(await this.controller.hodlerReward()) 56 | }) 57 | 58 | 59 | describe('new controller', function () { 60 | 61 | it('should fail to set new controller from other address than wallet', async function () { 62 | await this.controller.setNewController(1,{from:deployer}).should.be.rejectedWith(EVMThrow) 63 | await this.controller.setNewController(1,{from:investor}).should.be.rejectedWith(EVMThrow) 64 | }) 65 | 66 | it('should fail to set new controller to 0x0', async function () { 67 | await this.controller.setNewController(0,{from:wallet}).should.be.rejectedWith(EVMThrow) 68 | }) 69 | 70 | it('should set a new controller by wallet', async function () { 71 | // set controller to 0x3 72 | const preC = await this.token.balanceOf(this.controller.address) 73 | const preN = await this.token.balanceOf(3) 74 | await this.controller.setNewController(3,{from:wallet}).should.be.fulfilled 75 | const postC = await this.token.balanceOf(this.controller.address) 76 | const postN = await this.token.balanceOf(3) 77 | 78 | preC.should.be.bignumber.equal(postN.minus(preN)) 79 | postC.should.be.bignumber.equal(new BigNumber(0)) 80 | 81 | // token should change controller 82 | let owner = await this.token.controller() 83 | owner.should.equal('0x0000000000000000000000000000000000000003') 84 | 85 | // hodler should change controller 86 | owner = await this.hodler.owner() 87 | owner.should.equal('0x0000000000000000000000000000000000000003') 88 | }) 89 | 90 | it('should fail to set a new controller twice', async function () { 91 | await this.controller.setNewController(1,{from:wallet}).should.be.fulfilled 92 | await this.controller.setNewController(1,{from:wallet}).should.be.rejectedWith(EVMThrow) 93 | }) 94 | 95 | }) 96 | 97 | 98 | describe('new multisig', function () { 99 | 100 | it('should fail to set new multisig from other address than wallet', async function () { 101 | await this.controller.setNewMultisig(1,{from:deployer}).should.be.rejectedWith(EVMThrow) 102 | await this.controller.setNewMultisig(1,{from:investor}).should.be.rejectedWith(EVMThrow) 103 | }) 104 | 105 | it('should fail to set new multisig to zero', async function () { 106 | await this.controller.setNewMultisig(0,{from:wallet}).should.be.rejectedWith(EVMThrow) 107 | }) 108 | 109 | it('should set a new wallet by wallet', async function () { 110 | // set wallet to 0x3 111 | await this.controller.setNewMultisig(3,{from:wallet}).should.be.fulfilled 112 | 113 | const multisig = await this.controller.ethealMultisigWallet() 114 | multisig.should.equal('0x0000000000000000000000000000000000000003') 115 | }) 116 | 117 | }) 118 | 119 | 120 | describe('burn', function () { 121 | 122 | it('should fail to burn from other address than wallet', async function () { 123 | await this.controller.burn(this.controller.address,1,{from:deployer}).should.be.rejectedWith(EVMThrow) 124 | await this.controller.burn(this.controller.address,1,{from:investor}).should.be.rejectedWith(EVMThrow) 125 | }) 126 | 127 | it('should fail to burn tokens from other than controller or SALE address', async function () { 128 | await this.controller.burn(deployer,1,{from:wallet}).should.be.rejectedWith(EVMThrow) 129 | }) 130 | 131 | it('should burn tokens from controller address', async function () { 132 | const pre = await this.token.balanceOf(this.controller.address) 133 | const preT = await this.token.totalSupply() 134 | await this.controller.burn(this.controller.address,ether(1),{from:wallet}).should.be.fulfilled 135 | const post = await this.token.balanceOf(this.controller.address) 136 | const postT = await this.token.totalSupply() 137 | 138 | pre.minus(post).should.be.bignumber.equal(ether(1)) 139 | preT.minus(postT).should.be.bignumber.equal(ether(1)) 140 | }) 141 | 142 | it('should burn tokens from SALE address', async function () { 143 | const sale = await this.controller.SALE() 144 | const pre = await this.token.balanceOf(sale) 145 | const preT = await this.token.totalSupply() 146 | await this.controller.burn(sale,ether(1),{from:wallet}).should.be.fulfilled 147 | const post = await this.token.balanceOf(sale) 148 | const postT = await this.token.totalSupply() 149 | 150 | pre.minus(post).should.be.bignumber.equal(ether(1)) 151 | preT.minus(postT).should.be.bignumber.equal(ether(1)) 152 | }) 153 | 154 | }) 155 | 156 | 157 | describe('recover tokens', function () { 158 | 159 | it('should recover tokens from controller', async function () { 160 | // create new token 161 | await this.controller.unpause({from: deployer}) 162 | let newController = await Controller.new(wallet) 163 | let newToken = await Token.new(newController.address, this.factory.address) 164 | await newController.setEthealToken(newToken.address, 0) 165 | 166 | // create grant 167 | await newController.unpause({from: deployer}) 168 | await newController.createGrant(this.controller.address,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 169 | const _grant = Grant.at(await newController.tokenGrants(0)) 170 | 171 | // after duration we can get all the tokens 172 | await increaseTimeTo(this.startTime + duration.days(6*30+1)) 173 | await _grant.release(newToken.address).should.be.fulfilled 174 | 175 | let amount = await newToken.balanceOf(this.controller.address) 176 | amount.should.be.bignumber.equal(ether(1)) 177 | 178 | // recover the tokens 179 | amount = await newToken.balanceOf(investor) 180 | amount.should.be.bignumber.equal(ether(0)) 181 | await this.controller.extractTokens(newToken.address, investor).should.be.fulfilled 182 | amount = await newToken.balanceOf(investor) 183 | amount.should.be.bignumber.equal(ether(1)) 184 | }) 185 | 186 | it('should recover tokens from token', async function () { 187 | // create new token 188 | await this.controller.unpause({from: deployer}) 189 | let newController = await Controller.new(wallet) 190 | let newToken = await Token.new(newController.address, this.factory.address) 191 | await newController.setEthealToken(newToken.address, 0) 192 | 193 | // create grant 194 | await newController.unpause({from: deployer}) 195 | await newController.createGrant(this.token.address,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 196 | const _grant = Grant.at(await newController.tokenGrants(0)) 197 | 198 | // after duration we can get all the tokens 199 | await increaseTimeTo(this.startTime + duration.days(6*30+1)) 200 | await _grant.release(newToken.address).should.be.fulfilled 201 | 202 | let amount = await newToken.balanceOf(this.token.address) 203 | amount.should.be.bignumber.equal(ether(1)) 204 | 205 | // recover the tokens: to the controller 206 | amount = await newToken.balanceOf(this.controller.address) 207 | amount.should.be.bignumber.equal(ether(0)) 208 | await this.controller.claimTokenTokens(newToken.address).should.be.fulfilled 209 | amount = await newToken.balanceOf(this.controller.address) 210 | amount.should.be.bignumber.equal(ether(1)) 211 | }) 212 | 213 | }) 214 | 215 | 216 | it('should fail to send eth to the token', async function () { 217 | await this.token.send(ether(1)).should.be.rejectedWith(EVMThrow) 218 | }) 219 | 220 | 221 | describe('creating a valid crowdsale', function () { 222 | 223 | it('should fail with zero wallet', async function () { 224 | await Controller.new(0).should.be.rejectedWith(EVMThrow) 225 | }) 226 | 227 | }); 228 | 229 | 230 | describe('token distribution', function () { 231 | 232 | it('should create a total 100M tokens', async function () { 233 | const tokens = await this.token.totalSupply() 234 | tokens.should.be.bignumber.equal(ether(100000000)) 235 | }) 236 | 237 | it('should allocate 43M token to SALE address', async function () { 238 | const sale = await this.controller.SALE() 239 | const tokens = await this.token.balanceOf(sale) 240 | tokens.should.be.bignumber.equal(ether(43000000)) 241 | }) 242 | 243 | it('should allocate 20M token to wallet address', async function () { 244 | const tokens = await this.token.balanceOf(wallet) 245 | tokens.should.be.bignumber.equal(ether(20000000)) 246 | }) 247 | 248 | it('should allocate 10M token to HODL address', async function () { 249 | const tokens = await this.token.balanceOf(this.hodler.address) 250 | tokens.should.be.bignumber.equal(ether(10000000)) 251 | }) 252 | 253 | it('should allocate 20.5M token to controller address', async function () { 254 | const tokens = await this.token.balanceOf(this.controller.address) 255 | tokens.should.be.bignumber.equal(ether(20500000)) 256 | }) 257 | 258 | it('should allocate 3.5M token to deployer address', async function () { 259 | const tokens = await this.token.balanceOf(deployer) 260 | tokens.should.be.bignumber.equal(ether(3500000)) 261 | }) 262 | 263 | it('should allocate 3M token to investor address', async function () { 264 | let investor = await this.controller.INVESTOR1() 265 | let tokens = await this.token.balanceOf(investor) 266 | tokens.should.be.bignumber.equal(ether(2000000)) 267 | investor = await this.controller.INVESTOR2() 268 | tokens = await this.token.balanceOf(investor) 269 | tokens.should.be.bignumber.equal(ether(1000000)) 270 | }) 271 | 272 | }); 273 | 274 | 275 | describe('grants', function () { 276 | 277 | it('should not be created by anybody else than deployer', async function () { 278 | await this.controller.createGrant(advisor,this.startTime,ether(1),true,true,{from: investor}).should.be.rejectedWith(EVMThrow) 279 | await this.controller.createGrant(advisor,this.startTime,ether(1),true,true,{from: purchaser}).should.be.rejectedWith(EVMThrow) 280 | await this.controller.createGrant(advisor,this.startTime,ether(1),true,true,{from: advisor}).should.be.rejectedWith(EVMThrow) 281 | await this.controller.createGrant(advisor,this.startTime,ether(1),true,true,{from: wallet}).should.be.rejectedWith(EVMThrow) 282 | await this.controller.createGrant(advisor,this.startTime,ether(1),true,true,{from: teammate}).should.be.rejectedWith(EVMThrow) 283 | }) 284 | 285 | it('should be able to grant advisor tokens', async function () { 286 | // unpause controller 287 | await this.controller.unpause({from:deployer}) 288 | 289 | // create an advisor grant with 1 ether 290 | await this.controller.createGrant(advisor,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 291 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 292 | let _amount = await this.token.balanceOf(_grant.address) 293 | _amount.should.be.bignumber.equal(ether(1)) 294 | 295 | // before vesting release should throw 296 | await increaseTimeTo(this.startTime) 297 | await _grant.release(this.token.address).should.be.rejectedWith(EVMThrow) 298 | 299 | // after cliff we can get tokens 300 | await increaseTimeTo(this.startTime + duration.days(3*30)) 301 | 302 | let preG = await this.token.balanceOf(_grant.address) 303 | let preA = await this.token.balanceOf(advisor) 304 | await _grant.release(this.token.address).should.be.fulfilled 305 | let postG = await this.token.balanceOf(_grant.address) 306 | let postA = await this.token.balanceOf(advisor) 307 | 308 | preG.minus(postG).should.be.bignumber.above(ether(0)) 309 | postA.minus(preA).should.be.bignumber.above(ether(0)) 310 | 311 | // 3/4 of vesting time 312 | await increaseTimeTo(this.startTime + duration.days(9*15)) 313 | 314 | preG = await this.token.balanceOf(_grant.address) 315 | preA = await this.token.balanceOf(advisor) 316 | await _grant.release(this.token.address).should.be.fulfilled 317 | postG = await this.token.balanceOf(_grant.address) 318 | postA = await this.token.balanceOf(advisor) 319 | 320 | preG.minus(postG).should.be.bignumber.above(ether(0)) 321 | postA.minus(preA).should.be.bignumber.above(ether(0)) 322 | 323 | // total vesting 324 | await increaseTimeTo(this.startTime + duration.days(6*30)) 325 | 326 | preG = await this.token.balanceOf(_grant.address) 327 | preA = await this.token.balanceOf(advisor) 328 | await _grant.release(this.token.address).should.be.fulfilled 329 | postG = await this.token.balanceOf(_grant.address) 330 | postA = await this.token.balanceOf(advisor) 331 | 332 | preG.minus(postG).should.be.bignumber.above(ether(0)) 333 | postA.minus(preA).should.be.bignumber.above(ether(0)) 334 | 335 | // further vesting should throw 336 | await _grant.release(this.token.address).should.be.rejectedWith(EVMThrow) 337 | }) 338 | 339 | it('should be able to grant teammate tokens', async function () { 340 | // unpause controller 341 | await this.controller.unpause({from:deployer}) 342 | 343 | // create an advisor grant with 1 ether 344 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,false,{from: deployer}).should.be.fulfilled 345 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 346 | let _amount = await this.token.balanceOf(_grant.address) 347 | _amount.should.be.bignumber.equal(ether(1)) 348 | 349 | // before vesting release should throw 350 | await increaseTimeTo(this.startTime) 351 | await _grant.release(this.token.address).should.be.rejectedWith(EVMThrow) 352 | 353 | // after cliff we can get tokens 354 | await increaseTimeTo(this.startTime + duration.days(365)) 355 | 356 | let preG = await this.token.balanceOf(_grant.address) 357 | let preA = await this.token.balanceOf(teammate) 358 | await _grant.release(this.token.address).should.be.fulfilled 359 | let postG = await this.token.balanceOf(_grant.address) 360 | let postA = await this.token.balanceOf(teammate) 361 | 362 | preG.minus(postG).should.be.bignumber.above(ether(0)) 363 | postA.minus(preA).should.be.bignumber.above(ether(0)) 364 | 365 | // 3/4 of vesting time 366 | await increaseTimeTo(this.startTime + duration.days(3*365)) 367 | 368 | preG = await this.token.balanceOf(_grant.address) 369 | preA = await this.token.balanceOf(teammate) 370 | await _grant.release(this.token.address).should.be.fulfilled 371 | postG = await this.token.balanceOf(_grant.address) 372 | postA = await this.token.balanceOf(teammate) 373 | 374 | preG.minus(postG).should.be.bignumber.above(ether(0)) 375 | postA.minus(preA).should.be.bignumber.above(ether(0)) 376 | 377 | // total vesting 378 | await increaseTimeTo(this.startTime + duration.days(4*365)) 379 | 380 | preG = await this.token.balanceOf(_grant.address) 381 | preA = await this.token.balanceOf(teammate) 382 | await _grant.release(this.token.address).should.be.fulfilled 383 | postG = await this.token.balanceOf(_grant.address) 384 | postA = await this.token.balanceOf(teammate) 385 | 386 | preG.minus(postG).should.be.bignumber.above(ether(0)) 387 | postA.minus(preA).should.be.bignumber.above(ether(0)) 388 | 389 | // further vesting should throw 390 | await _grant.release(this.token.address).should.be.rejectedWith(EVMThrow) 391 | }) 392 | 393 | it('should be able to increase token grant', async function () { 394 | // create an advisor grant with 1 ether 395 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 396 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 397 | await this.controller.transferToGrant(0,ether(1),{from: deployer}).should.be.fulfilled 398 | let _amount = await this.token.balanceOf(_grant.address) 399 | _amount.should.be.bignumber.equal(ether(2)) 400 | 401 | // after start we can't add more tokens 402 | await increaseTimeTo(this.startTime+duration.seconds(1)) 403 | await this.controller.transferToGrant(0,ether(1),{from: deployer}).should.be.rejectedWith(EVMThrow) 404 | }) 405 | 406 | it('should not be able to revoke non-revokable grant', async function () { 407 | // create an advisor grant with 1 ether 408 | await this.controller.createGrant(teammate,this.startTime,ether(1),false,true,{from: deployer}).should.be.fulfilled 409 | await this.controller.revokeGrant(0).should.be.rejectedWith(EVMThrow) 410 | }) 411 | 412 | it('should not be able to revoke an already revoked grant', async function () { 413 | // create an advisor grant with 1 ether 414 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 415 | await this.controller.revokeGrant(0).should.be.fulfilled 416 | await this.controller.revokeGrant(0).should.be.rejectedWith(EVMThrow) 417 | }) 418 | 419 | it('should be able to revoke before grant start', async function () { 420 | // create an advisor grant with 1 ether 421 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 422 | await this.controller.revokeGrant(0).should.be.fulfilled 423 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 424 | let _amount = await this.token.balanceOf(_grant.address) 425 | _amount.should.be.bignumber.equal(ether(0)) 426 | }) 427 | 428 | it('should be able to revoke before cliff start', async function () { 429 | // create an advisor grant with 1 ether 430 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 431 | await increaseTimeTo(this.startTime + duration.days(30)) 432 | 433 | await this.controller.revokeGrant(0).should.be.fulfilled 434 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 435 | let _amount = await this.token.balanceOf(_grant.address) 436 | _amount.should.be.bignumber.equal(ether(0)) 437 | }) 438 | 439 | it('should be able to revoke after cliff', async function () { 440 | // create an advisor grant with 1 ether 441 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 442 | await increaseTimeTo(this.startTime + duration.days(3*30)) 443 | 444 | await this.controller.revokeGrant(0).should.be.fulfilled 445 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 446 | let _amount = await this.token.balanceOf(_grant.address) 447 | _amount.should.be.bignumber.below(ether(1)) 448 | _amount.should.be.bignumber.above(ether(0)) 449 | }) 450 | 451 | it('should all tokens remain in vesting if revoked after vesting end', async function () { 452 | // create an advisor grant with 1 ether 453 | await this.controller.createGrant(teammate,this.startTime,ether(1),true,true,{from: deployer}).should.be.fulfilled 454 | await increaseTimeTo(this.startTime + duration.days(6*30+1)) 455 | 456 | await this.controller.revokeGrant(0).should.be.fulfilled 457 | let _grant = Grant.at(await this.controller.tokenGrants(0)) 458 | let _amount = await this.token.balanceOf(_grant.address) 459 | _amount.should.be.bignumber.equal(ether(1)) 460 | }) 461 | 462 | }) 463 | 464 | }) -------------------------------------------------------------------------------- /contracts/EthealNormalSale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./ERC20MiniMe.sol"; 4 | import "./CappedCrowdsale.sol"; 5 | import "./TokenController.sol"; 6 | import "./FinalizableCrowdsale.sol"; 7 | import "./Pausable.sol"; 8 | import "./EthealWhitelist.sol"; 9 | 10 | /** 11 | * @title EthealNormalSale 12 | * @author thesved 13 | * @notice Etheal Token Sale contract, with softcap and hardcap (cap) 14 | * @dev This contract has to be finalized before token claims are enabled 15 | */ 16 | contract EthealNormalSale is Pausable, FinalizableCrowdsale, CappedCrowdsale { 17 | // the token is here 18 | TokenController public ethealController; 19 | 20 | // after reaching {weiRaised} >= {softCap}, there is {softCapTime} seconds until the sale closes 21 | // {softCapClose} contains the closing time 22 | uint256 public rate = 700; 23 | uint256 public softCap = 6800 ether; 24 | uint256 public softCapTime = 120 hours; 25 | uint256 public softCapClose; 26 | uint256 public cap = 14300 ether; 27 | 28 | // how many token is sold and not claimed, used for refunding to token controller 29 | uint256 public tokenBalance; 30 | 31 | // total token sold 32 | uint256 public tokenSold; 33 | 34 | // minimum contribution, 0.1ETH 35 | uint256 public minContribution = 0.1 ether; 36 | 37 | // whitelist: above threshold the contract has to approve each transaction 38 | EthealWhitelist public whitelist; 39 | uint256 public whitelistThreshold = 1 ether; 40 | 41 | // deposit address from which it can get funds before sale 42 | address public deposit; 43 | 44 | // stakes contains token bought and contirbutions contains the value in wei 45 | mapping (address => uint256) public stakes; 46 | mapping (address => uint256) public contributions; 47 | 48 | // promo token bonus 49 | address public promoTokenController; 50 | mapping (address => uint256) public bonusExtra; 51 | 52 | // addresses of contributors to handle finalization after token sale end (refunds or token claims) 53 | address[] public contributorsKeys; 54 | 55 | // events for token purchase during sale and claiming tokens after sale 56 | event LogTokenClaimed(address indexed _claimer, address indexed _beneficiary, uint256 _amount); 57 | event LogTokenPurchase(address indexed _purchaser, address indexed _beneficiary, uint256 _value, uint256 _amount, uint256 _participants, uint256 _weiRaised); 58 | event LogTokenSoftCapReached(uint256 _closeTime); 59 | event LogTokenHardCapReached(); 60 | 61 | //////////////// 62 | // Constructor and inherited function overrides 63 | //////////////// 64 | 65 | /// @notice Constructor to create PreSale contract 66 | /// @param _ethealController Address of ethealController 67 | /// @param _startTime The start time of token sale in seconds. 68 | /// @param _endTime The end time of token sale in seconds. 69 | /// @param _minContribution The minimum contribution per transaction in wei (0.1 ETH) 70 | /// @param _rate Number of HEAL tokens per 1 ETH 71 | /// @param _softCap Softcap in wei, reaching it ends the sale in _softCapTime seconds 72 | /// @param _softCapTime Seconds until the sale remains open after reaching _softCap 73 | /// @param _cap Maximum cap in wei, we can't raise more funds 74 | /// @param _wallet Address of multisig wallet, which will get all the funds after successful sale 75 | function EthealNormalSale( 76 | address _ethealController, 77 | uint256 _startTime, 78 | uint256 _endTime, 79 | uint256 _minContribution, 80 | uint256 _rate, 81 | uint256 _softCap, 82 | uint256 _softCapTime, 83 | uint256 _cap, 84 | address _wallet 85 | ) 86 | CappedCrowdsale(_cap) 87 | FinalizableCrowdsale() 88 | Crowdsale(_startTime, _endTime, _rate, _wallet) 89 | { 90 | // ethealController must be valid 91 | require(_ethealController != address(0)); 92 | ethealController = TokenController(_ethealController); 93 | 94 | // caps have to be consistent with each other 95 | require(_softCap <= _cap); 96 | softCap = _softCap; 97 | softCapTime = _softCapTime; 98 | 99 | // this is needed since super constructor wont overwite overriden variables 100 | cap = _cap; 101 | rate = _rate; 102 | 103 | minContribution = _minContribution; 104 | } 105 | 106 | //////////////// 107 | // Administer contract details 108 | //////////////// 109 | 110 | /// @notice Sets min contribution 111 | function setMinContribution(uint256 _minContribution) public onlyOwner { 112 | minContribution = _minContribution; 113 | } 114 | 115 | /// @notice Sets soft cap and max cap 116 | function setCaps(uint256 _softCap, uint256 _softCapTime, uint256 _cap) public onlyOwner { 117 | require(_softCap <= _cap); 118 | softCap = _softCap; 119 | softCapTime = _softCapTime; 120 | cap = _cap; 121 | } 122 | 123 | /// @notice Sets crowdsale start and end time 124 | function setTimes(uint256 _startTime, uint256 _endTime) public onlyOwner { 125 | require(_startTime <= _endTime); 126 | require(!hasEnded()); 127 | startTime = _startTime; 128 | endTime = _endTime; 129 | } 130 | 131 | /// @notice Set rate 132 | function setRate(uint256 _rate) public onlyOwner { 133 | require(_rate > 0); 134 | rate = _rate; 135 | } 136 | 137 | /// @notice Set address of promo token 138 | function setPromoTokenController(address _addr) public onlyOwner { 139 | require(_addr != address(0)); 140 | promoTokenController = _addr; 141 | } 142 | 143 | /// @notice Set whitelist contract address and minimum threshold 144 | function setWhitelist(address _whitelist, uint256 _threshold) public onlyOwner { 145 | // if whitelist contract address is provided we set it 146 | if (_whitelist != address(0)) { 147 | whitelist = EthealWhitelist(_whitelist); 148 | } 149 | whitelistThreshold = _threshold; 150 | } 151 | 152 | /// @notice Set deposit contract address from which it can receive money before sale 153 | function setDeposit(address _deposit) public onlyOwner { 154 | deposit = _deposit; 155 | } 156 | 157 | //////////////// 158 | // Purchase functions 159 | //////////////// 160 | 161 | /// @dev Overriding Crowdsale#buyTokens to add partial refund 162 | /// @param _beneficiary Beneficiary of the token purchase 163 | function buyTokens(address _beneficiary) public payable whenNotPaused { 164 | handlePayment(_beneficiary, msg.value, now, ""); 165 | } 166 | 167 | /// @dev buying tokens for someone with offchain whitelist signature 168 | function buyTokens(address _beneficiary, bytes _whitelistSign) public payable whenNotPaused { 169 | handlePayment(_beneficiary, msg.value, now, _whitelistSign); 170 | } 171 | 172 | /// @dev Internal function for handling transactions with ether. 173 | function handlePayment(address _beneficiary, uint256 _amount, uint256 _time, bytes memory _whitelistSign) internal { 174 | require(_beneficiary != address(0)); 175 | 176 | uint256 weiAmount = handleContribution(_beneficiary, _amount, _time, _whitelistSign); 177 | forwardFunds(weiAmount); 178 | 179 | // handle refund excess tokens 180 | uint256 refund = _amount.sub(weiAmount); 181 | if (refund > 0) { 182 | _beneficiary.transfer(refund); 183 | } 184 | } 185 | 186 | /// @dev Handling the amount of contribution and cap logic. Internal function. 187 | /// @return Wei successfully contributed. 188 | function handleContribution(address _beneficiary, uint256 _amount, uint256 _time, bytes memory _whitelistSign) internal returns (uint256) { 189 | require(_beneficiary != address(0)); 190 | 191 | uint256 weiToCap = howMuchCanXContributeNow(_beneficiary); 192 | uint256 weiAmount = uint256Min(weiToCap, _amount); 193 | 194 | // account the new contribution 195 | transferToken(_beneficiary, weiAmount, _time, _whitelistSign); 196 | 197 | // close sale in softCapTime seconds after reaching softCap 198 | if (weiRaised >= softCap && softCapClose == 0) { 199 | softCapClose = now.add(softCapTime); 200 | LogTokenSoftCapReached(uint256Min(softCapClose, endTime)); 201 | } 202 | 203 | // event for hard cap reached 204 | if (weiRaised >= cap) { 205 | LogTokenHardCapReached(); 206 | } 207 | 208 | return weiAmount; 209 | } 210 | 211 | /// @dev Handling token distribution and accounting. Overriding Crowdsale#transferToken. 212 | /// @param _beneficiary Address of the recepient of the tokens 213 | /// @param _weiAmount Contribution in wei 214 | /// @param _time When the contribution was made 215 | function transferToken(address _beneficiary, uint256 _weiAmount, uint256 _time, bytes memory _whitelistSign) internal { 216 | require(_beneficiary != address(0)); 217 | require(validPurchase(_weiAmount)); 218 | 219 | // increase wei Raised 220 | weiRaised = weiRaised.add(_weiAmount); 221 | 222 | // require whitelist above threshold 223 | contributions[_beneficiary] = contributions[_beneficiary].add(_weiAmount); 224 | require(contributions[_beneficiary] <= whitelistThreshold 225 | || whitelist.isWhitelisted(_beneficiary) 226 | || whitelist.isOffchainWhitelisted(_beneficiary, _whitelistSign) 227 | ); 228 | 229 | // calculate tokens, so we can refund excess tokens to EthealController after token sale 230 | uint256 _bonus = getBonus(_beneficiary, _weiAmount, _time); 231 | uint256 tokens = _weiAmount.mul(rate).mul(_bonus).div(100); 232 | tokenBalance = tokenBalance.add(tokens); 233 | 234 | if (stakes[_beneficiary] == 0) { 235 | contributorsKeys.push(_beneficiary); 236 | } 237 | stakes[_beneficiary] = stakes[_beneficiary].add(tokens); 238 | 239 | LogTokenPurchase(msg.sender, _beneficiary, _weiAmount, tokens, contributorsKeys.length, weiRaised); 240 | } 241 | 242 | /// @dev Get eth deposit from Deposit contract 243 | function depositEth(address _beneficiary, uint256 _time, bytes _whitelistSign) public payable whenNotPaused { 244 | require(msg.sender == deposit); 245 | 246 | handlePayment(_beneficiary, msg.value, _time, _whitelistSign); 247 | } 248 | 249 | /// @dev Deposit from other currencies 250 | function depositOffchain(address _beneficiary, uint256 _amount, uint256 _time, bytes _whitelistSign) public onlyOwner whenNotPaused { 251 | handleContribution(_beneficiary, _amount, _time, _whitelistSign); 252 | } 253 | 254 | /// @dev Overriding Crowdsale#validPurchase to add min contribution logic 255 | /// @param _weiAmount Contribution amount in wei 256 | /// @return true if contribution is okay 257 | function validPurchase(uint256 _weiAmount) internal constant returns (bool) { 258 | bool nonEnded = !hasEnded(); 259 | bool nonZero = _weiAmount != 0; 260 | bool enoughContribution = _weiAmount >= minContribution; 261 | return nonEnded && nonZero && enoughContribution; 262 | } 263 | 264 | /// @dev Overriding Crowdsale#hasEnded to add soft cap logic 265 | /// @return true if crowdsale event has ended or a softCapClose time is set and passed 266 | function hasEnded() public constant returns (bool) { 267 | return super.hasEnded() || softCapClose > 0 && now > softCapClose; 268 | } 269 | 270 | /// @dev Extending RefundableCrowdsale#finalization sending back excess tokens to ethealController 271 | function finalization() internal { 272 | uint256 _balance = getHealBalance(); 273 | 274 | // saving token balance for future reference 275 | tokenSold = tokenBalance; 276 | 277 | // send back the excess token to ethealController 278 | if (_balance > tokenBalance) { 279 | ethealController.ethealToken().transfer(ethealController.SALE(), _balance.sub(tokenBalance)); 280 | } 281 | 282 | // hodler stake counting starts 14 days after closing normal sale 283 | ethealController.setHodlerTime(now + 14 days); 284 | 285 | super.finalization(); 286 | } 287 | 288 | 289 | //////////////// 290 | // AFTER token sale 291 | //////////////// 292 | 293 | /// @notice Modifier for after sale finalization 294 | modifier afterSale() { 295 | require(isFinalized); 296 | _; 297 | } 298 | 299 | /// @notice Claim token for msg.sender after token sale based on stake. 300 | function claimToken() public afterSale { 301 | claimTokenFor(msg.sender); 302 | } 303 | 304 | /// @notice Claim token after token sale based on stake. 305 | /// @dev Anyone can call this function and distribute tokens after successful token sale 306 | /// @param _beneficiary Address of the beneficiary who gets the token 307 | function claimTokenFor(address _beneficiary) public afterSale whenNotPaused { 308 | uint256 tokens = stakes[_beneficiary]; 309 | require(tokens > 0); 310 | 311 | // set the stake 0 for beneficiary 312 | stakes[_beneficiary] = 0; 313 | 314 | // decrease tokenBalance, to make it possible to withdraw excess HEAL funds 315 | tokenBalance = tokenBalance.sub(tokens); 316 | 317 | // distribute hodlr stake 318 | ethealController.addHodlerStake(_beneficiary, tokens); 319 | 320 | // distribute token 321 | require(ethealController.ethealToken().transfer(_beneficiary, tokens)); 322 | LogTokenClaimed(msg.sender, _beneficiary, tokens); 323 | } 324 | 325 | /// @notice claimToken() for multiple addresses 326 | /// @dev Anyone can call this function and distribute tokens after successful token sale 327 | /// @param _beneficiaries Array of addresses for which we want to claim tokens 328 | function claimManyTokenFor(address[] _beneficiaries) external afterSale { 329 | for (uint256 i = 0; i < _beneficiaries.length; i++) { 330 | claimTokenFor(_beneficiaries[i]); 331 | } 332 | } 333 | 334 | 335 | //////////////// 336 | // Bonus functions 337 | //////////////// 338 | 339 | /// @notice Sets extra 5% bonus for those addresses who send back a promo token 340 | /// @notice It contains an easter egg. 341 | /// @param _addr this address gets the bonus 342 | /// @param _value how many tokens are transferred 343 | function setPromoBonus(address _addr, uint256 _value) public { 344 | require(msg.sender == promoTokenController || msg.sender == owner); 345 | require(_value>0); 346 | 347 | uint256 _bonus = keccak256(_value) == 0xbeced09521047d05b8960b7e7bcc1d1292cf3e4b2a6b63f48335cbde5f7545d2 ? 6 : 5; 348 | 349 | if (bonusExtra[ _addr ] < _bonus) { 350 | bonusExtra[ _addr ] = _bonus; 351 | } 352 | } 353 | 354 | /// @notice Manual set extra bonus for addresses 355 | function setBonusExtra(address _addr, uint256 _bonus) public onlyOwner { 356 | require(_addr != address(0)); 357 | bonusExtra[_addr] = _bonus; 358 | } 359 | 360 | /// @notice Mass set extra bonus for addresses 361 | function setManyBonusExtra(address[] _addr, uint256 _bonus) external onlyOwner { 362 | for (uint256 i = 0; i < _addr.length; i++) { 363 | setBonusExtra(_addr[i],_bonus); 364 | } 365 | } 366 | 367 | /// @notice Returns bonus for now 368 | function getBonusNow(address _addr, uint256 _size) public view returns (uint256) { 369 | return getBonus(_addr, _size, now); 370 | } 371 | 372 | /// @notice Returns the bonus in percentage, eg 130 means 30% bonus 373 | function getBonus(address _addr, uint256 _size, uint256 _time) public view returns (uint256 _bonus) { 374 | // detailed bonus structure: https://etheal.com/#heal-token 375 | _bonus = 100; 376 | 377 | // time based bonuses 378 | uint256 _day = getSaleDay(_time); 379 | uint256 _hour = getSaleHour(_time); 380 | if (_day <= 1) { 381 | if (_hour <= 1) _bonus = 130; 382 | else if (_hour <= 5) _bonus = 125; 383 | else if (_hour <= 8) _bonus = 120; 384 | else _bonus = 118; 385 | } 386 | else if (_day <= 2) { _bonus = 116; } 387 | else if (_day <= 3) { _bonus = 115; } 388 | else if (_day <= 5) { _bonus = 114; } 389 | else if (_day <= 7) { _bonus = 113; } 390 | else if (_day <= 9) { _bonus = 112; } 391 | else if (_day <= 11) { _bonus = 111; } 392 | else if (_day <= 13) { _bonus = 110; } 393 | else if (_day <= 15) { _bonus = 108; } 394 | else if (_day <= 17) { _bonus = 107; } 395 | else if (_day <= 19) { _bonus = 106; } 396 | else if (_day <= 21) { _bonus = 105; } 397 | else if (_day <= 23) { _bonus = 104; } 398 | else if (_day <= 25) { _bonus = 103; } 399 | else if (_day <= 27) { _bonus = 102; } 400 | 401 | // size based bonuses 402 | if (_size >= 100 ether) { _bonus = _bonus + 4; } 403 | else if (_size >= 10 ether) { _bonus = _bonus + 2; } 404 | 405 | // manual bonus 406 | _bonus += bonusExtra[ _addr ]; 407 | 408 | return _bonus; 409 | } 410 | 411 | 412 | //////////////// 413 | // Constant, helper functions 414 | //////////////// 415 | 416 | /// @notice How many wei can the msg.sender contribute now. 417 | function howMuchCanIContributeNow() view public returns (uint256) { 418 | return howMuchCanXContributeNow(msg.sender); 419 | } 420 | 421 | /// @notice How many wei can an ethereum address contribute now. 422 | /// @param _beneficiary Ethereum address 423 | /// @return Number of wei the _beneficiary can contribute now. 424 | function howMuchCanXContributeNow(address _beneficiary) view public returns (uint256) { 425 | require(_beneficiary != address(0)); 426 | 427 | if (hasEnded() || paused) 428 | return 0; 429 | 430 | // wei to hard cap 431 | uint256 weiToCap = cap.sub(weiRaised); 432 | 433 | return weiToCap; 434 | } 435 | 436 | /// @notice For a give date how many 24 hour blocks have ellapsed since token sale start 437 | /// Before sale return 0, first day 1, second day 2, ... 438 | /// @param _time Date in seconds for which we want to know which sale day it is 439 | /// @return Number of 24 hour blocks ellapsing since token sale start starting from 1 440 | function getSaleDay(uint256 _time) view public returns (uint256) { 441 | uint256 _day = 0; 442 | if (_time > startTime) { 443 | _day = _time.sub(startTime).div(60*60*24).add(1); 444 | } 445 | return _day; 446 | } 447 | 448 | /// @notice How many 24 hour blocks have ellapsed since token sale start 449 | /// @return Number of 24 hour blocks ellapsing since token sale start starting from 1 450 | function getSaleDayNow() view public returns (uint256) { 451 | return getSaleDay(now); 452 | } 453 | 454 | /// @notice Returns sale hour: 0 before sale, 1 for the first hour, ... 455 | /// @param _time Date in seconds for which we want to know which sale hour it is 456 | /// @return Number of 1 hour blocks ellapsing since token sale start starting from 1 457 | function getSaleHour(uint256 _time) view public returns (uint256) { 458 | uint256 _hour = 0; 459 | if (_time > startTime) { 460 | _hour = _time.sub(startTime).div(60*60).add(1); 461 | } 462 | return _hour; 463 | } 464 | 465 | /// @notice How many 1 hour blocks have ellapsed since token sale start 466 | /// @return Number of 1 hour blocks ellapsing since token sale start starting from 1 467 | function getSaleHourNow() view public returns (uint256) { 468 | return getSaleHour(now); 469 | } 470 | 471 | /// @notice Minimum between two uint256 numbers 472 | function uint256Min(uint256 a, uint256 b) pure internal returns (uint256) { 473 | return a > b ? b : a; 474 | } 475 | 476 | 477 | //////////////// 478 | // Test and contribution web app, NO audit is needed 479 | //////////////// 480 | 481 | /// @notice How many contributors we have. 482 | /// @return Number of different contributor ethereum addresses 483 | function getContributorsCount() view public returns (uint256) { 484 | return contributorsKeys.length; 485 | } 486 | 487 | /// @notice Get contributor addresses to manage refunds or token claims. 488 | /// @dev If the sale is not yet successful, then it searches in the RefundVault. 489 | /// If the sale is successful, it searches in contributors. 490 | /// @param _pending If true, then returns addresses which didn't get their tokens distributed to them 491 | /// @param _claimed If true, then returns already distributed addresses 492 | /// @return Array of addresses of contributors 493 | function getContributors(bool _pending, bool _claimed) view public returns (address[] contributors) { 494 | uint256 i = 0; 495 | uint256 results = 0; 496 | address[] memory _contributors = new address[](contributorsKeys.length); 497 | 498 | // search in contributors 499 | for (i = 0; i < contributorsKeys.length; i++) { 500 | if (_pending && stakes[contributorsKeys[i]] > 0 || _claimed && stakes[contributorsKeys[i]] == 0) { 501 | _contributors[results] = contributorsKeys[i]; 502 | results++; 503 | } 504 | } 505 | 506 | contributors = new address[](results); 507 | for (i = 0; i < results; i++) { 508 | contributors[i] = _contributors[i]; 509 | } 510 | 511 | return contributors; 512 | } 513 | 514 | /// @notice How many HEAL tokens do this contract have 515 | function getHealBalance() view public returns (uint256) { 516 | return ethealController.ethealToken().balanceOf(address(this)); 517 | } 518 | } -------------------------------------------------------------------------------- /contracts/EthealPreSale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | import "./ERC20MiniMe.sol"; 4 | import "./CappedCrowdsale.sol"; 5 | import "./RefundableCrowdsale.sol"; 6 | import "./TokenController.sol"; 7 | import "./Pausable.sol"; 8 | 9 | 10 | /** 11 | * @title EthealPreSale 12 | * @author thesved 13 | * @notice Etheal Token Sale round one presale contract, with mincap (goal), softcap and hardcap (cap) 14 | * @dev This contract has to be finalized before refund or token claims are enabled 15 | */ 16 | contract EthealPreSale is Pausable, CappedCrowdsale, RefundableCrowdsale { 17 | // the token is here 18 | TokenController public ethealController; 19 | 20 | // after reaching {weiRaised} >= {softCap}, there is {softCapTime} seconds until the sale closes 21 | // {softCapClose} contains the closing time 22 | uint256 public rate = 1250; 23 | uint256 public goal = 333 ether; 24 | uint256 public softCap = 3600 ether; 25 | uint256 public softCapTime = 120 hours; 26 | uint256 public softCapClose; 27 | uint256 public cap = 7200 ether; 28 | 29 | // how many token is sold and not claimed, used for refunding to token controller 30 | uint256 public tokenBalance; 31 | 32 | // total token sold 33 | uint256 public tokenSold; 34 | 35 | // contributing above {maxGasPrice} results in 36 | // calculating stakes on {maxGasPricePenalty} / 100 37 | // eg. 80 {maxGasPricePenalty} means 80%, sending 5 ETH with more than 100gwei gas price will be calculated as 4 ETH 38 | uint256 public maxGasPrice = 100 * 10**9; 39 | uint256 public maxGasPricePenalty = 80; 40 | 41 | // minimum contribution, 0.1ETH 42 | uint256 public minContribution = 0.1 ether; 43 | 44 | // first {whitelistDayCount} days of token sale is exclusive for whitelisted addresses 45 | // {whitelistDayMaxStake} contains the max stake limits per address for each whitelist sales day 46 | // {whitelist} contains who can contribute during whitelist period 47 | uint8 public whitelistDayCount; 48 | mapping (address => bool) public whitelist; 49 | mapping (uint8 => uint256) public whitelistDayMaxStake; 50 | 51 | // stakes contains contribution stake in wei 52 | // contributed ETH is calculated on 80% when sending funds with gasprice above maxGasPrice 53 | mapping (address => uint256) public stakes; 54 | 55 | // addresses of contributors to handle finalization after token sale end (refunds or token claims) 56 | address[] public contributorsKeys; 57 | 58 | // events for token purchase during sale and claiming tokens after sale 59 | event TokenClaimed(address indexed _claimer, address indexed _beneficiary, uint256 _stake, uint256 _amount); 60 | event TokenPurchase(address indexed _purchaser, address indexed _beneficiary, uint256 _value, uint256 _stake, uint256 _amount, uint256 _participants, uint256 _weiRaised); 61 | event TokenGoalReached(); 62 | event TokenSoftCapReached(uint256 _closeTime); 63 | 64 | // whitelist events for adding days with maximum stakes and addresses 65 | event WhitelistAddressAdded(address indexed _whitelister, address indexed _beneficiary); 66 | event WhitelistAddressRemoved(address indexed _whitelister, address indexed _beneficiary); 67 | event WhitelistSetDay(address indexed _whitelister, uint8 _day, uint256 _maxStake); 68 | 69 | 70 | //////////////// 71 | // Constructor and inherited function overrides 72 | //////////////// 73 | 74 | /// @notice Constructor to create PreSale contract 75 | /// @param _ethealController Address of ethealController 76 | /// @param _startTime The start time of token sale in seconds. 77 | /// @param _endTime The end time of token sale in seconds. 78 | /// @param _minContribution The minimum contribution per transaction in wei (0.1 ETH) 79 | /// @param _rate Number of HEAL tokens per 1 ETH 80 | /// @param _goal Minimum funding in wei, below that EVERYONE gets back ALL their 81 | /// contributions regardless of maxGasPrice penalty. 82 | /// Eg. someone contributes with 5 ETH, but gets only 4 ETH stakes because 83 | /// sending funds with gasprice over 100Gwei, he will still get back >>5 ETH<< 84 | /// in case of unsuccessful token sale 85 | /// @param _softCap Softcap in wei, reaching it ends the sale in _softCapTime seconds 86 | /// @param _softCapTime Seconds until the sale remains open after reaching _softCap 87 | /// @param _cap Maximum cap in wei, we can't raise more funds 88 | /// @param _gasPrice Maximum gas price 89 | /// @param _gasPenalty Penalty in percentage points for calculating stakes, eg. 80 means calculating 90 | /// stakes on 80% if gasprice was higher than _gasPrice 91 | /// @param _wallet Address of multisig wallet, which will get all the funds after successful sale 92 | function EthealPreSale( 93 | address _ethealController, 94 | uint256 _startTime, 95 | uint256 _endTime, 96 | uint256 _minContribution, 97 | uint256 _rate, 98 | uint256 _goal, 99 | uint256 _softCap, 100 | uint256 _softCapTime, 101 | uint256 _cap, 102 | uint256 _gasPrice, 103 | uint256 _gasPenalty, 104 | address _wallet 105 | ) 106 | CappedCrowdsale(_cap) 107 | FinalizableCrowdsale() 108 | RefundableCrowdsale(_goal) 109 | Crowdsale(_startTime, _endTime, _rate, _wallet) 110 | { 111 | // ethealController must be valid 112 | require(_ethealController != address(0)); 113 | ethealController = TokenController(_ethealController); 114 | 115 | // caps have to be consistent with each other 116 | require(_goal <= _softCap && _softCap <= _cap); 117 | 118 | 119 | softCap = _softCap; 120 | softCapTime = _softCapTime; 121 | 122 | // this is needed since super constructor wont overwite overriden variables 123 | cap = _cap; 124 | goal = _goal; 125 | rate = _rate; 126 | 127 | maxGasPrice = _gasPrice; 128 | maxGasPricePenalty = _gasPenalty; 129 | 130 | minContribution = _minContribution; 131 | } 132 | 133 | /// @dev Overriding Crowdsale#buyTokens to add partial refund and softcap logic 134 | /// @param _beneficiary Beneficiary of the token purchase 135 | function buyTokens(address _beneficiary) public payable whenNotPaused { 136 | require(_beneficiary != address(0)); 137 | 138 | uint256 weiToCap = howMuchCanXContributeNow(_beneficiary); 139 | uint256 weiAmount = uint256Min(weiToCap, msg.value); 140 | 141 | // goal is reached 142 | if (weiRaised < goal && weiRaised.add(weiAmount) >= goal) 143 | TokenGoalReached(); 144 | 145 | // call the Crowdsale#buyTokens internal function 146 | buyTokens(_beneficiary, weiAmount); 147 | 148 | // close sale in softCapTime seconds after reaching softCap 149 | if (weiRaised >= softCap && softCapClose == 0) { 150 | softCapClose = now.add(softCapTime); 151 | TokenSoftCapReached(uint256Min(softCapClose, endTime)); 152 | } 153 | 154 | // handle refund 155 | uint256 refund = msg.value.sub(weiAmount); 156 | if (refund > 0) { 157 | msg.sender.transfer(refund); 158 | } 159 | } 160 | 161 | /// @dev Overriding Crowdsale#transferToken, which keeps track of contributions DURING token sale 162 | /// @param _beneficiary Address of the recepient of the tokens 163 | /// @param _weiAmount Contribution in wei 164 | function transferToken(address _beneficiary, uint256 _weiAmount) internal { 165 | require(_beneficiary != address(0)); 166 | 167 | uint256 weiAmount = _weiAmount; 168 | // check maxGasPricePenalty 169 | if (maxGasPrice > 0 && tx.gasprice > maxGasPrice) { 170 | weiAmount = weiAmount.mul(maxGasPricePenalty).div(100); 171 | } 172 | 173 | // calculate tokens, so we can refund excess tokens to EthealController after token sale 174 | uint256 tokens = weiAmount.mul(rate); 175 | tokenBalance = tokenBalance.add(tokens); 176 | 177 | if (stakes[_beneficiary] == 0) 178 | contributorsKeys.push(_beneficiary); 179 | 180 | stakes[_beneficiary] = stakes[_beneficiary].add(weiAmount); 181 | 182 | TokenPurchase(msg.sender, _beneficiary, _weiAmount, weiAmount, tokens, contributorsKeys.length, weiRaised); 183 | } 184 | 185 | /// @dev Overriding Crowdsale#validPurchase to add min contribution logic 186 | /// @param _weiAmount Contribution amount in wei 187 | /// @return true if contribution is okay 188 | function validPurchase(uint256 _weiAmount) internal constant returns (bool) { 189 | return super.validPurchase(_weiAmount) && _weiAmount >= minContribution; 190 | } 191 | 192 | /// @dev Overriding Crowdsale#hasEnded to add soft cap logic 193 | /// @return true if crowdsale event has ended or a softCapClose time is set and passed 194 | function hasEnded() public constant returns (bool) { 195 | return super.hasEnded() || softCapClose > 0 && now > softCapClose; 196 | } 197 | 198 | /// @dev Overriding RefundableCrowdsale#claimRefund to enable anyone to call for any address 199 | /// which enables us to refund anyone and also anyone can refund themselves 200 | function claimRefund() public { 201 | claimRefundFor(msg.sender); 202 | } 203 | 204 | /// @dev Extending RefundableCrowdsale#finalization sending back excess tokens to ethealController 205 | function finalization() internal { 206 | uint256 _balance = getHealBalance(); 207 | 208 | // if token sale was successful send back excess funds 209 | if (goalReached()) { 210 | // saving token balance for future reference 211 | tokenSold = tokenBalance; 212 | 213 | // send back the excess token to ethealController 214 | if (_balance > tokenBalance) { 215 | ethealController.ethealToken().transfer(ethealController.SALE(), _balance.sub(tokenBalance)); 216 | } 217 | } else if (!goalReached() && _balance > 0) { 218 | // if token sale is failed, then send back all tokens to ethealController's sale address 219 | tokenBalance = 0; 220 | ethealController.ethealToken().transfer(ethealController.SALE(), _balance); 221 | } 222 | 223 | super.finalization(); 224 | } 225 | 226 | 227 | //////////////// 228 | // BEFORE token sale 229 | //////////////// 230 | 231 | /// @notice Modifier for before sale cases 232 | modifier beforeSale() { 233 | require(!hasStarted()); 234 | _; 235 | } 236 | 237 | /// @notice Sets whitelist 238 | /// @dev The length of _whitelistLimits says that the first X days of token sale is 239 | /// closed, meaning only for whitelisted addresses. 240 | /// @param _add Array of addresses to add to whitelisted ethereum accounts 241 | /// @param _remove Array of addresses to remove to whitelisted ethereum accounts 242 | /// @param _whitelistLimits Array of limits in wei, where _whitelistLimits[0] = 10 ETH means 243 | /// whitelisted addresses can contribute maximum 10 ETH stakes on the first day 244 | /// After _whitelistLimits.length days, there will be no limits per address (besides hard cap) 245 | function setWhitelist(address[] _add, address[] _remove, uint256[] _whitelistLimits) public onlyOwner beforeSale { 246 | uint256 i = 0; 247 | uint8 j = 0; // access max daily stakes 248 | 249 | // we override whiteListLimits only if it was supplied as an argument 250 | if (_whitelistLimits.length > 0) { 251 | // saving whitelist max stake limits for each day -> uint256 maxStakeLimit 252 | whitelistDayCount = uint8(_whitelistLimits.length); 253 | 254 | for (i = 0; i < _whitelistLimits.length; i++) { 255 | j = uint8(i.add(1)); 256 | if (whitelistDayMaxStake[j] != _whitelistLimits[i]) { 257 | whitelistDayMaxStake[j] = _whitelistLimits[i]; 258 | WhitelistSetDay(msg.sender, j, _whitelistLimits[i]); 259 | } 260 | } 261 | } 262 | 263 | // adding whitelist addresses 264 | for (i = 0; i < _add.length; i++) { 265 | require(_add[i] != address(0)); 266 | 267 | if (!whitelist[_add[i]]) { 268 | whitelist[_add[i]] = true; 269 | WhitelistAddressAdded(msg.sender, _add[i]); 270 | } 271 | } 272 | 273 | // removing whitelist addresses 274 | for (i = 0; i < _remove.length; i++) { 275 | require(_remove[i] != address(0)); 276 | 277 | if (whitelist[_remove[i]]) { 278 | whitelist[_remove[i]] = false; 279 | WhitelistAddressRemoved(msg.sender, _remove[i]); 280 | } 281 | } 282 | } 283 | 284 | /// @notice Sets max gas price and penalty before sale 285 | function setMaxGas(uint256 _maxGas, uint256 _penalty) public onlyOwner beforeSale { 286 | maxGasPrice = _maxGas; 287 | maxGasPricePenalty = _penalty; 288 | } 289 | 290 | /// @notice Sets min contribution before sale 291 | function setMinContribution(uint256 _minContribution) public onlyOwner beforeSale { 292 | minContribution = _minContribution; 293 | } 294 | 295 | /// @notice Sets minimum goal, soft cap and max cap 296 | function setCaps(uint256 _goal, uint256 _softCap, uint256 _softCapTime, uint256 _cap) public onlyOwner beforeSale { 297 | goal = _goal; 298 | softCap = _softCap; 299 | softCapTime = _softCapTime; 300 | cap = _cap; 301 | } 302 | 303 | /// @notice Sets crowdsale start and end time 304 | function setTimes(uint256 _startTime, uint256 _endTime) public onlyOwner beforeSale { 305 | startTime = _startTime; 306 | endTime = _endTime; 307 | } 308 | 309 | /// @notice Set rate 310 | function setRate(uint256 _rate) public onlyOwner beforeSale { 311 | rate = _rate; 312 | } 313 | 314 | 315 | //////////////// 316 | // AFTER token sale 317 | //////////////// 318 | 319 | /// @notice Modifier for cases where sale is failed 320 | /// @dev It checks whether we haven't reach the minimum goal AND whether the contract is finalized 321 | modifier afterSaleFail() { 322 | require(!goalReached() && isFinalized); 323 | _; 324 | } 325 | 326 | /// @notice Modifier for cases where sale is closed and was successful. 327 | /// @dev It checks whether 328 | /// the sale has ended 329 | /// and we have reached our goal 330 | /// AND whether the contract is finalized 331 | modifier afterSaleSuccess() { 332 | require(goalReached() && isFinalized); 333 | _; 334 | } 335 | 336 | /// @notice Modifier for after sale finalization 337 | modifier afterSale() { 338 | require(isFinalized); 339 | _; 340 | } 341 | 342 | 343 | /// @notice Refund an ethereum address 344 | /// @param _beneficiary Address we want to refund 345 | function claimRefundFor(address _beneficiary) public afterSaleFail whenNotPaused { 346 | require(_beneficiary != address(0)); 347 | vault.refund(_beneficiary); 348 | } 349 | 350 | /// @notice Refund several addresses with one call 351 | /// @param _beneficiaries Array of addresses we want to refund 352 | function claimRefundsFor(address[] _beneficiaries) external afterSaleFail { 353 | for (uint256 i = 0; i < _beneficiaries.length; i++) 354 | claimRefundFor(_beneficiaries[i]); 355 | } 356 | 357 | /// @notice Claim token for msg.sender after token sale based on stake. 358 | function claimToken() public afterSaleSuccess { 359 | claimTokenFor(msg.sender); 360 | } 361 | 362 | /// @notice Claim token after token sale based on stake. 363 | /// @dev Anyone can call this function and distribute tokens after successful token sale 364 | /// @param _beneficiary Address of the beneficiary who gets the token 365 | function claimTokenFor(address _beneficiary) public afterSaleSuccess whenNotPaused { 366 | uint256 stake = stakes[_beneficiary]; 367 | require(stake > 0); 368 | 369 | // set the stake 0 for beneficiary 370 | stakes[_beneficiary] = 0; 371 | 372 | // calculate token count 373 | uint256 tokens = stake.mul(rate); 374 | 375 | // decrease tokenBalance, to make it possible to withdraw excess HEAL funds 376 | tokenBalance = tokenBalance.sub(tokens); 377 | 378 | // distribute hodlr stake 379 | ethealController.addHodlerStake(_beneficiary, tokens); 380 | 381 | // distribute token 382 | require(ethealController.ethealToken().transfer(_beneficiary, tokens)); 383 | TokenClaimed(msg.sender, _beneficiary, stake, tokens); 384 | } 385 | 386 | /// @notice claimToken() for multiple addresses 387 | /// @dev Anyone can call this function and distribute tokens after successful token sale 388 | /// @param _beneficiaries Array of addresses for which we want to claim tokens 389 | function claimTokensFor(address[] _beneficiaries) external afterSaleSuccess { 390 | for (uint256 i = 0; i < _beneficiaries.length; i++) 391 | claimTokenFor(_beneficiaries[i]); 392 | } 393 | 394 | /// @notice Get back accidentally sent token from the vault 395 | function extractVaultTokens(address _token, address _claimer) public onlyOwner afterSale { 396 | require(_claimer != address(0)); 397 | 398 | vault.extractTokens(_token, _claimer); 399 | } 400 | 401 | 402 | //////////////// 403 | // Constant, helper functions 404 | //////////////// 405 | 406 | /// @notice How many wei can the msg.sender contribute now. 407 | function howMuchCanIContributeNow() view public returns (uint256) { 408 | return howMuchCanXContributeNow(msg.sender); 409 | } 410 | 411 | /// @notice How many wei can an ethereum address contribute now. 412 | /// @dev This function can return 0 when the crowdsale is stopped 413 | /// or the address has maxed the current day's whitelist cap, 414 | /// it is possible, that next day he can contribute 415 | /// @param _beneficiary Ethereum address 416 | /// @return Number of wei the _beneficiary can contribute now. 417 | function howMuchCanXContributeNow(address _beneficiary) view public returns (uint256) { 418 | require(_beneficiary != address(0)); 419 | 420 | if (!hasStarted() || hasEnded()) 421 | return 0; 422 | 423 | // wei to hard cap 424 | uint256 weiToCap = cap.sub(weiRaised); 425 | 426 | // if this is a whitelist limited period 427 | uint8 _saleDay = getSaleDayNow(); 428 | if (_saleDay <= whitelistDayCount) { 429 | // address can't contribute if 430 | // it is not whitelisted 431 | if (!whitelist[_beneficiary]) 432 | return 0; 433 | 434 | // personal cap is the daily whitelist limit minus the stakes the address already has 435 | uint256 weiToPersonalCap = whitelistDayMaxStake[_saleDay].sub(stakes[_beneficiary]); 436 | 437 | // calculate for maxGasPrice penalty 438 | if (msg.value > 0 && maxGasPrice > 0 && tx.gasprice > maxGasPrice) 439 | weiToPersonalCap = weiToPersonalCap.mul(100).div(maxGasPricePenalty); 440 | 441 | weiToCap = uint256Min(weiToCap, weiToPersonalCap); 442 | } 443 | 444 | return weiToCap; 445 | } 446 | 447 | /// @notice For a give date how many 24 hour blocks have ellapsed since token sale start 448 | /// @dev _time has to be bigger than the startTime of token sale, otherwise SafeMath's div will throw. 449 | /// Within 24 hours of token sale it will return 1, 450 | /// between 24 and 48 hours it will return 2, etc. 451 | /// @param _time Date in seconds for which we want to know which sale day it is 452 | /// @return Number of 24 hour blocks ellapsing since token sale start starting from 1 453 | function getSaleDay(uint256 _time) view public returns (uint8) { 454 | return uint8(_time.sub(startTime).div(60*60*24).add(1)); 455 | } 456 | 457 | /// @notice How many 24 hour blocks have ellapsed since token sale start 458 | /// @return Number of 24 hour blocks ellapsing since token sale start starting from 1 459 | function getSaleDayNow() view public returns (uint8) { 460 | return getSaleDay(now); 461 | } 462 | 463 | /// @notice Minimum between two uint8 numbers 464 | function uint8Min(uint8 a, uint8 b) pure internal returns (uint8) { 465 | return a > b ? b : a; 466 | } 467 | 468 | /// @notice Minimum between two uint256 numbers 469 | function uint256Min(uint256 a, uint256 b) pure internal returns (uint256) { 470 | return a > b ? b : a; 471 | } 472 | 473 | 474 | //////////////// 475 | // Test and contribution web app, NO audit is needed 476 | //////////////// 477 | 478 | /// @notice Was this token sale successful? 479 | /// @return true if the sale is over and we have reached the minimum goal 480 | function wasSuccess() view public returns (bool) { 481 | return hasEnded() && goalReached(); 482 | } 483 | 484 | /// @notice How many contributors we have. 485 | /// @return Number of different contributor ethereum addresses 486 | function getContributorsCount() view public returns (uint256) { 487 | return contributorsKeys.length; 488 | } 489 | 490 | /// @notice Get contributor addresses to manage refunds or token claims. 491 | /// @dev If the sale is not yet successful, then it searches in the RefundVault. 492 | /// If the sale is successful, it searches in contributors. 493 | /// @param _pending If true, then returns addresses which didn't get refunded or their tokens distributed to them 494 | /// @param _claimed If true, then returns already refunded or token distributed addresses 495 | /// @return Array of addresses of contributors 496 | function getContributors(bool _pending, bool _claimed) view public returns (address[] contributors) { 497 | uint256 i = 0; 498 | uint256 results = 0; 499 | address[] memory _contributors = new address[](contributorsKeys.length); 500 | 501 | // if we have reached our goal, then search in contributors, since this is what we want to monitor 502 | if (goalReached()) { 503 | for (i = 0; i < contributorsKeys.length; i++) { 504 | if (_pending && stakes[contributorsKeys[i]] > 0 || _claimed && stakes[contributorsKeys[i]] == 0) { 505 | _contributors[results] = contributorsKeys[i]; 506 | results++; 507 | } 508 | } 509 | } else { 510 | // otherwise search in the refund vault 511 | for (i = 0; i < contributorsKeys.length; i++) { 512 | if (_pending && vault.deposited(contributorsKeys[i]) > 0 || _claimed && vault.deposited(contributorsKeys[i]) == 0) { 513 | _contributors[results] = contributorsKeys[i]; 514 | results++; 515 | } 516 | } 517 | } 518 | 519 | contributors = new address[](results); 520 | for (i = 0; i < results; i++) { 521 | contributors[i] = _contributors[i]; 522 | } 523 | 524 | return contributors; 525 | } 526 | 527 | /// @notice How many HEAL tokens do this contract have 528 | function getHealBalance() view public returns (uint256) { 529 | return ethealController.ethealToken().balanceOf(address(this)); 530 | } 531 | } -------------------------------------------------------------------------------- /contracts/MiniMeToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | /// @title MiniMeToken Contract 4 | /// @author Jordi Baylina 5 | /// @dev This token contract's goal is to make it easy for anyone to clone this 6 | /// token using the token distribution at a given block, this will allow DAO's 7 | /// and DApps to upgrade their features in a decentralized manner without 8 | /// affecting the original token 9 | /// @dev It is ERC20 compliant, but still needs to under go further testing. 10 | 11 | import "./Controlled.sol"; 12 | import "./TokenController.sol"; 13 | 14 | contract ApproveAndCallFallBack { 15 | function receiveApproval(address from, uint256 _amount, address _token, bytes _data) public; 16 | } 17 | 18 | /// @dev The actual token contract, the default controller is the msg.sender 19 | /// that deploys the contract, so usually this token will be deployed by a 20 | /// token controller contract, which Giveth will call a "Campaign" 21 | contract MiniMeToken is Controlled { 22 | 23 | string public name; //The Token's name: e.g. DigixDAO Tokens 24 | uint8 public decimals; //Number of decimals of the smallest unit 25 | string public symbol; //An identifier: e.g. REP 26 | string public version = 'MMT_0.2'; //An arbitrary versioning scheme 27 | 28 | 29 | /// @dev `Checkpoint` is the structure that attaches a block number to a 30 | /// given value, the block number attached is the one that last changed the 31 | /// value 32 | struct Checkpoint { 33 | 34 | // `fromBlock` is the block number that the value was generated from 35 | uint128 fromBlock; 36 | 37 | // `value` is the amount of tokens at a specific block number 38 | uint128 value; 39 | } 40 | 41 | // `parentToken` is the Token address that was cloned to produce this token; 42 | // it will be 0x0 for a token that was not cloned 43 | MiniMeToken public parentToken; 44 | 45 | // `parentSnapShotBlock` is the block number from the Parent Token that was 46 | // used to determine the initial distribution of the Clone Token 47 | uint public parentSnapShotBlock; 48 | 49 | // `creationBlock` is the block number that the Clone Token was created 50 | uint public creationBlock; 51 | 52 | // `balances` is the map that tracks the balance of each address, in this 53 | // contract when the balance changes the block number that the change 54 | // occurred is also included in the map 55 | mapping (address => Checkpoint[]) balances; 56 | 57 | // `allowed` tracks any extra transfer rights as in all ERC20 tokens 58 | mapping (address => mapping (address => uint256)) allowed; 59 | 60 | // Tracks the history of the `totalSupply` of the token 61 | Checkpoint[] totalSupplyHistory; 62 | 63 | // Flag that determines if the token is transferable or not. 64 | bool public transfersEnabled; 65 | 66 | // The factory used to create new clone tokens 67 | MiniMeTokenFactory public tokenFactory; 68 | 69 | //////////////// 70 | // Constructor 71 | //////////////// 72 | 73 | /// @notice Constructor to create a MiniMeToken 74 | /// @param _tokenFactory The address of the MiniMeTokenFactory contract that 75 | /// will create the Clone token contracts, the token factory needs to be 76 | /// deployed first 77 | /// @param _parentToken Address of the parent token, set to 0x0 if it is a 78 | /// new token 79 | /// @param _parentSnapShotBlock Block of the parent token that will 80 | /// determine the initial distribution of the clone token, set to 0 if it 81 | /// is a new token 82 | /// @param _tokenName Name of the new token 83 | /// @param _decimalUnits Number of decimals of the new token 84 | /// @param _tokenSymbol Token Symbol for the new token 85 | /// @param _transfersEnabled If true, tokens will be able to be transferred 86 | function MiniMeToken( 87 | address _tokenFactory, 88 | address _parentToken, 89 | uint _parentSnapShotBlock, 90 | string _tokenName, 91 | uint8 _decimalUnits, 92 | string _tokenSymbol, 93 | bool _transfersEnabled 94 | ) public { 95 | tokenFactory = MiniMeTokenFactory(_tokenFactory); 96 | name = _tokenName; // Set the name 97 | decimals = _decimalUnits; // Set the decimals 98 | symbol = _tokenSymbol; // Set the symbol 99 | parentToken = MiniMeToken(_parentToken); 100 | parentSnapShotBlock = _parentSnapShotBlock; 101 | transfersEnabled = _transfersEnabled; 102 | creationBlock = block.number; 103 | } 104 | 105 | 106 | /////////////////// 107 | // ERC20 Methods 108 | /////////////////// 109 | 110 | /// @notice Send `_amount` tokens to `_to` from `msg.sender` 111 | /// @param _to The address of the recipient 112 | /// @param _amount The amount of tokens to be transferred 113 | /// @return Whether the transfer was successful or not 114 | function transfer(address _to, uint256 _amount) public returns (bool success) { 115 | require(transfersEnabled); 116 | doTransfer(msg.sender, _to, _amount); 117 | return true; 118 | } 119 | 120 | /// @notice Send `_amount` tokens to `_to` from `_from` on the condition it 121 | /// is approved by `_from` 122 | /// @param _from The address holding the tokens being transferred 123 | /// @param _to The address of the recipient 124 | /// @param _amount The amount of tokens to be transferred 125 | /// @return True if the transfer was successful 126 | function transferFrom(address _from, address _to, uint256 _amount 127 | ) public returns (bool success) { 128 | 129 | // The controller of this contract can move tokens around at will, 130 | // this is important to recognize! Confirm that you trust the 131 | // controller of this contract, which in most situations should be 132 | // another open source smart contract or 0x0 133 | if (msg.sender != controller) { 134 | require(transfersEnabled); 135 | 136 | // The standard ERC 20 transferFrom functionality 137 | require(allowed[_from][msg.sender] >= _amount); 138 | allowed[_from][msg.sender] -= _amount; 139 | } 140 | doTransfer(_from, _to, _amount); 141 | return true; 142 | } 143 | 144 | /// @dev This is the actual transfer function in the token contract, it can 145 | /// only be called by other functions in this contract. 146 | /// @param _from The address holding the tokens being transferred 147 | /// @param _to The address of the recipient 148 | /// @param _amount The amount of tokens to be transferred 149 | /// @return True if the transfer was successful 150 | function doTransfer(address _from, address _to, uint _amount 151 | ) internal { 152 | 153 | if (_amount == 0) { 154 | Transfer(_from, _to, _amount); // Follow the spec to louch the event when transfer 0 155 | return; 156 | } 157 | 158 | require(parentSnapShotBlock < block.number); 159 | 160 | // Do not allow transfer to 0x0 or the token contract itself 161 | require((_to != 0) && (_to != address(this))); 162 | 163 | // Alerts the token controller of the transfer 164 | if (isContract(controller)) { 165 | require(TokenController(controller).onTransfer(_from, _to, _amount)); 166 | } 167 | 168 | // If the amount being transfered is more than the balance of the 169 | // account the transfer throws 170 | var previousBalanceFrom = balanceOfAt(_from, block.number); 171 | 172 | require(previousBalanceFrom >= _amount); 173 | 174 | // First update the balance array with the new value for the address 175 | // sending the tokens 176 | updateValueAtNow(balances[_from], previousBalanceFrom - _amount); 177 | 178 | // Then update the balance array with the new value for the address 179 | // receiving the tokens 180 | var previousBalanceTo = balanceOfAt(_to, block.number); 181 | require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow 182 | updateValueAtNow(balances[_to], previousBalanceTo + _amount); 183 | 184 | // An event to make the transfer easy to find on the blockchain 185 | Transfer(_from, _to, _amount); 186 | 187 | } 188 | 189 | /// @param _owner The address that's balance is being requested 190 | /// @return The balance of `_owner` at the current block 191 | function balanceOf(address _owner) public constant returns (uint256 balance) { 192 | return balanceOfAt(_owner, block.number); 193 | } 194 | 195 | /// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on 196 | /// its behalf. This is a modified version of the ERC20 approve function 197 | /// to be a little bit safer 198 | /// @param _spender The address of the account able to transfer the tokens 199 | /// @param _amount The amount of tokens to be approved for transfer 200 | /// @return True if the approval was successful 201 | function approve(address _spender, uint256 _amount) public returns (bool success) { 202 | require(transfersEnabled); 203 | 204 | // To change the approve amount you first have to reduce the addresses` 205 | // allowance to zero by calling `approve(_spender,0)` if it is not 206 | // already 0 to mitigate the race condition described here: 207 | // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 208 | require((_amount == 0) || (allowed[msg.sender][_spender] == 0)); 209 | 210 | // Alerts the token controller of the approve function call 211 | if (isContract(controller)) { 212 | require(TokenController(controller).onApprove(msg.sender, _spender, _amount)); 213 | } 214 | 215 | allowed[msg.sender][_spender] = _amount; 216 | Approval(msg.sender, _spender, _amount); 217 | return true; 218 | } 219 | 220 | /// @dev This function makes it easy to read the `allowed[]` map 221 | /// @param _owner The address of the account that owns the token 222 | /// @param _spender The address of the account able to transfer the tokens 223 | /// @return Amount of remaining tokens of _owner that _spender is allowed 224 | /// to spend 225 | function allowance(address _owner, address _spender 226 | ) public constant returns (uint256 remaining) { 227 | return allowed[_owner][_spender]; 228 | } 229 | 230 | /// @notice `msg.sender` approves `_spender` to send `_amount` tokens on 231 | /// its behalf, and then a function is triggered in the contract that is 232 | /// being approved, `_spender`. This allows users to use their tokens to 233 | /// interact with contracts in one function call instead of two 234 | /// @param _spender The address of the contract able to transfer the tokens 235 | /// @param _amount The amount of tokens to be approved for transfer 236 | /// @return True if the function call was successful 237 | function approveAndCall(address _spender, uint256 _amount, bytes _extraData 238 | ) public returns (bool success) { 239 | require(approve(_spender, _amount)); 240 | 241 | ApproveAndCallFallBack(_spender).receiveApproval( 242 | msg.sender, 243 | _amount, 244 | this, 245 | _extraData 246 | ); 247 | 248 | return true; 249 | } 250 | 251 | /// @dev This function makes it easy to get the total number of tokens 252 | /// @return The total number of tokens 253 | function totalSupply() public constant returns (uint) { 254 | return totalSupplyAt(block.number); 255 | } 256 | 257 | 258 | //////////////// 259 | // Query balance and totalSupply in History 260 | //////////////// 261 | 262 | /// @dev Queries the balance of `_owner` at a specific `_blockNumber` 263 | /// @param _owner The address from which the balance will be retrieved 264 | /// @param _blockNumber The block number when the balance is queried 265 | /// @return The balance at `_blockNumber` 266 | function balanceOfAt(address _owner, uint _blockNumber) public constant 267 | returns (uint) { 268 | 269 | // These next few lines are used when the balance of the token is 270 | // requested before a check point was ever created for this token, it 271 | // requires that the `parentToken.balanceOfAt` be queried at the 272 | // genesis block for that token as this contains initial balance of 273 | // this token 274 | if ((balances[_owner].length == 0) 275 | || (balances[_owner][0].fromBlock > _blockNumber)) { 276 | if (address(parentToken) != 0) { 277 | return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock)); 278 | } else { 279 | // Has no parent 280 | return 0; 281 | } 282 | 283 | // This will return the expected balance during normal situations 284 | } else { 285 | return getValueAt(balances[_owner], _blockNumber); 286 | } 287 | } 288 | 289 | /// @notice Total amount of tokens at a specific `_blockNumber`. 290 | /// @param _blockNumber The block number when the totalSupply is queried 291 | /// @return The total amount of tokens at `_blockNumber` 292 | function totalSupplyAt(uint _blockNumber) public constant returns(uint) { 293 | 294 | // These next few lines are used when the totalSupply of the token is 295 | // requested before a check point was ever created for this token, it 296 | // requires that the `parentToken.totalSupplyAt` be queried at the 297 | // genesis block for this token as that contains totalSupply of this 298 | // token at this block number. 299 | if ((totalSupplyHistory.length == 0) 300 | || (totalSupplyHistory[0].fromBlock > _blockNumber)) { 301 | if (address(parentToken) != 0) { 302 | return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock)); 303 | } else { 304 | return 0; 305 | } 306 | 307 | // This will return the expected totalSupply during normal situations 308 | } else { 309 | return getValueAt(totalSupplyHistory, _blockNumber); 310 | } 311 | } 312 | 313 | //////////////// 314 | // Clone Token Method 315 | //////////////// 316 | 317 | /// @notice Creates a new clone token with the initial distribution being 318 | /// this token at `_snapshotBlock` 319 | /// @param _cloneTokenName Name of the clone token 320 | /// @param _cloneDecimalUnits Number of decimals of the smallest unit 321 | /// @param _cloneTokenSymbol Symbol of the clone token 322 | /// @param _snapshotBlock Block when the distribution of the parent token is 323 | /// copied to set the initial distribution of the new clone token; 324 | /// if the block is zero than the actual block, the current block is used 325 | /// @param _transfersEnabled True if transfers are allowed in the clone 326 | /// @return The address of the new MiniMeToken Contract 327 | function createCloneToken( 328 | string _cloneTokenName, 329 | uint8 _cloneDecimalUnits, 330 | string _cloneTokenSymbol, 331 | uint _snapshotBlock, 332 | bool _transfersEnabled 333 | ) public returns(address) { 334 | if (_snapshotBlock == 0) _snapshotBlock = block.number; 335 | MiniMeToken cloneToken = tokenFactory.createCloneToken( 336 | this, 337 | _snapshotBlock, 338 | _cloneTokenName, 339 | _cloneDecimalUnits, 340 | _cloneTokenSymbol, 341 | _transfersEnabled 342 | ); 343 | 344 | cloneToken.changeController(msg.sender); 345 | 346 | // An event to make the token easy to find on the blockchain 347 | NewCloneToken(address(cloneToken), _snapshotBlock); 348 | return address(cloneToken); 349 | } 350 | 351 | //////////////// 352 | // Generate and destroy tokens 353 | //////////////// 354 | 355 | /// @notice Generates `_amount` tokens that are assigned to `_owner` 356 | /// @param _owner The address that will be assigned the new tokens 357 | /// @param _amount The quantity of tokens generated 358 | /// @return True if the tokens are generated correctly 359 | function generateTokens(address _owner, uint _amount 360 | ) public onlyController returns (bool) { 361 | uint curTotalSupply = totalSupply(); 362 | require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow 363 | uint previousBalanceTo = balanceOf(_owner); 364 | require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow 365 | updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount); 366 | updateValueAtNow(balances[_owner], previousBalanceTo + _amount); 367 | Transfer(0, _owner, _amount); 368 | return true; 369 | } 370 | 371 | 372 | /// @notice Burns `_amount` tokens from `_owner` 373 | /// @param _owner The address that will lose the tokens 374 | /// @param _amount The quantity of tokens to burn 375 | /// @return True if the tokens are burned correctly 376 | function destroyTokens(address _owner, uint _amount 377 | ) onlyController public returns (bool) { 378 | uint curTotalSupply = totalSupply(); 379 | require(curTotalSupply >= _amount); 380 | uint previousBalanceFrom = balanceOf(_owner); 381 | require(previousBalanceFrom >= _amount); 382 | updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount); 383 | updateValueAtNow(balances[_owner], previousBalanceFrom - _amount); 384 | Transfer(_owner, 0, _amount); 385 | return true; 386 | } 387 | 388 | //////////////// 389 | // Enable tokens transfers 390 | //////////////// 391 | 392 | 393 | /// @notice Enables token holders to transfer their tokens freely if true 394 | /// @param _transfersEnabled True if transfers are allowed in the clone 395 | function enableTransfers(bool _transfersEnabled) public onlyController { 396 | transfersEnabled = _transfersEnabled; 397 | } 398 | 399 | //////////////// 400 | // Internal helper functions to query and set a value in a snapshot array 401 | //////////////// 402 | 403 | /// @dev `getValueAt` retrieves the number of tokens at a given block number 404 | /// @param checkpoints The history of values being queried 405 | /// @param _block The block number to retrieve the value at 406 | /// @return The number of tokens being queried 407 | function getValueAt(Checkpoint[] storage checkpoints, uint _block 408 | ) constant internal returns (uint) { 409 | if (checkpoints.length == 0) return 0; 410 | 411 | // Shortcut for the actual value 412 | if (_block >= checkpoints[checkpoints.length-1].fromBlock) 413 | return checkpoints[checkpoints.length-1].value; 414 | if (_block < checkpoints[0].fromBlock) return 0; 415 | 416 | // Binary search of the value in the array 417 | uint min = 0; 418 | uint max = checkpoints.length-1; 419 | while (max > min) { 420 | uint mid = (max + min + 1)/ 2; 421 | if (checkpoints[mid].fromBlock<=_block) { 422 | min = mid; 423 | } else { 424 | max = mid-1; 425 | } 426 | } 427 | return checkpoints[min].value; 428 | } 429 | 430 | /// @dev `updateValueAtNow` used to update the `balances` map and the 431 | /// `totalSupplyHistory` 432 | /// @param checkpoints The history of data being updated 433 | /// @param _value The new number of tokens 434 | function updateValueAtNow(Checkpoint[] storage checkpoints, uint _value 435 | ) internal { 436 | if ((checkpoints.length == 0) 437 | || (checkpoints[checkpoints.length -1].fromBlock < block.number)) { 438 | Checkpoint storage newCheckPoint = checkpoints[ checkpoints.length++ ]; 439 | newCheckPoint.fromBlock = uint128(block.number); 440 | newCheckPoint.value = uint128(_value); 441 | } else { 442 | Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1]; 443 | oldCheckPoint.value = uint128(_value); 444 | } 445 | } 446 | 447 | /// @dev Internal function to determine if an address is a contract 448 | /// @param _addr The address being queried 449 | /// @return True if `_addr` is a contract 450 | function isContract(address _addr) constant internal returns(bool) { 451 | uint size; 452 | if (_addr == 0) return false; 453 | assembly { 454 | size := extcodesize(_addr) 455 | } 456 | return size>0; 457 | } 458 | 459 | /// @dev Helper function to return a min betwen the two uints 460 | function min(uint a, uint b) pure internal returns (uint) { 461 | return a < b ? a : b; 462 | } 463 | 464 | /// @notice The fallback function: If the contract's controller has not been 465 | /// set to 0, then the `proxyPayment` method is called which relays the 466 | /// ether and creates tokens as described in the token controller contract 467 | function () public payable { 468 | require(isContract(controller)); 469 | require(TokenController(controller).proxyPayment.value(msg.value)(msg.sender)); 470 | } 471 | 472 | ////////// 473 | // Safety Methods 474 | ////////// 475 | 476 | /// @notice This method can be used by the controller to extract mistakenly 477 | /// sent tokens to this contract. 478 | /// @param _token The address of the token contract that you want to recover 479 | /// set to 0 in case you want to extract ether. 480 | function claimTokens(address _token) public onlyController { 481 | if (_token == 0x0) { 482 | controller.transfer(this.balance); 483 | return; 484 | } 485 | 486 | MiniMeToken token = MiniMeToken(_token); 487 | uint balance = token.balanceOf(this); 488 | token.transfer(controller, balance); 489 | ClaimedTokens(_token, controller, balance); 490 | } 491 | 492 | //////////////// 493 | // Events 494 | //////////////// 495 | event ClaimedTokens(address indexed _token, address indexed _controller, uint _amount); 496 | event Transfer(address indexed _from, address indexed _to, uint256 _amount); 497 | event NewCloneToken(address indexed _cloneToken, uint _snapshotBlock); 498 | event Approval( 499 | address indexed _owner, 500 | address indexed _spender, 501 | uint256 _amount 502 | ); 503 | 504 | } 505 | 506 | 507 | //////////////// 508 | // MiniMeTokenFactory 509 | //////////////// 510 | 511 | /// @dev This contract is used to generate clone contracts from a contract. 512 | /// In solidity this is the way to create a contract from a contract of the 513 | /// same class 514 | contract MiniMeTokenFactory { 515 | 516 | /// @notice Update the DApp by creating a new token with new functionalities 517 | /// the msg.sender becomes the controller of this clone token 518 | /// @param _parentToken Address of the token being cloned 519 | /// @param _snapshotBlock Block of the parent token that will 520 | /// determine the initial distribution of the clone token 521 | /// @param _tokenName Name of the new token 522 | /// @param _decimalUnits Number of decimals of the new token 523 | /// @param _tokenSymbol Token Symbol for the new token 524 | /// @param _transfersEnabled If true, tokens will be able to be transferred 525 | /// @return The address of the new token contract 526 | function createCloneToken( 527 | address _parentToken, 528 | uint _snapshotBlock, 529 | string _tokenName, 530 | uint8 _decimalUnits, 531 | string _tokenSymbol, 532 | bool _transfersEnabled 533 | ) public returns (MiniMeToken) { 534 | MiniMeToken newToken = new MiniMeToken( 535 | this, 536 | _parentToken, 537 | _snapshotBlock, 538 | _tokenName, 539 | _decimalUnits, 540 | _tokenSymbol, 541 | _transfersEnabled 542 | ); 543 | 544 | newToken.changeController(msg.sender); 545 | return newToken; 546 | } 547 | } --------------------------------------------------------------------------------