├── .gitattributes ├── ISimpleCrowdsale.sol ├── AbyssToken.sol ├── token ├── ITokenEventListener.sol ├── IERC20Token.sol ├── ERC20Token.sol ├── LockedTokens.sol ├── TransferLimitedToken.sol └── ManagedToken.sol ├── fund ├── IPollManagedFund.sol ├── ICrowdsaleFund.sol └── ICrowdsaleReservationFund.sol ├── math └── SafeMath.sol ├── LICENSE ├── Pausable.sol ├── ownership ├── Ownable.sol └── MultiOwnable.sol ├── TapPoll.sol ├── RefundPoll.sol ├── ReservationFund.sol ├── poll └── BasePoll.sol ├── PollManagedFund.sol ├── DateTime.sol ├── Fund.sol └── Crowdsale.sol /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /ISimpleCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | 4 | interface ISimpleCrowdsale { 5 | function getSoftCap() external view returns(uint256); 6 | function isContributorInLists(address contributorAddress) external view returns(bool); 7 | function processReservationFundContribution( 8 | address contributor, 9 | uint256 tokenAmount, 10 | uint256 tokenBonusAmount 11 | ) external payable; 12 | } -------------------------------------------------------------------------------- /AbyssToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './token/TransferLimitedToken.sol'; 4 | 5 | 6 | contract ABYSS is TransferLimitedToken { 7 | uint256 public constant SALE_END_TIME = 1526479200; // 16.05.2018 14:00:00 UTC 8 | 9 | function ABYSS(address _listener, address[] _owners, address manager) public 10 | TransferLimitedToken(SALE_END_TIME, _listener, _owners, manager) 11 | { 12 | name = "ABYSS"; 13 | symbol = "ABYSS"; 14 | decimals = 18; 15 | } 16 | } -------------------------------------------------------------------------------- /token/ITokenEventListener.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | /** 4 | * @title ITokenEventListener 5 | * @dev Interface which should be implemented by token listener 6 | */ 7 | interface ITokenEventListener { 8 | /** 9 | * @dev Function is called after token transfer/transferFrom 10 | * @param _from Sender address 11 | * @param _to Receiver address 12 | * @param _value Amount of tokens 13 | */ 14 | function onTokenTransfer(address _from, address _to, uint256 _value) external; 15 | } -------------------------------------------------------------------------------- /fund/IPollManagedFund.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | /** 4 | * @title IPollManagedFund 5 | * @dev Fund callbacks used by polling contracts 6 | */ 7 | interface IPollManagedFund { 8 | /** 9 | * @dev TapPoll callback 10 | * @param agree True if new tap value is accepted by majority of contributors 11 | * @param _tap New tap value 12 | */ 13 | function onTapPollFinish(bool agree, uint256 _tap) external; 14 | 15 | /** 16 | * @dev RefundPoll callback 17 | * @param agree True if contributors decided to allow refunding 18 | */ 19 | function onRefundPollFinish(bool agree) external; 20 | } -------------------------------------------------------------------------------- /fund/ICrowdsaleFund.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | /** 4 | * @title ICrowdsaleFund 5 | * @dev Fund methods used by crowdsale contract 6 | */ 7 | interface ICrowdsaleFund { 8 | /** 9 | * @dev Function accepts user`s contributed ether and logs contribution 10 | * @param contributor Contributor wallet address. 11 | */ 12 | function processContribution(address contributor) external payable; 13 | /** 14 | * @dev Function is called on the end of successful crowdsale 15 | */ 16 | function onCrowdsaleEnd() external; 17 | /** 18 | * @dev Function is called if crowdsale failed to reach soft cap 19 | */ 20 | function enableCrowdsaleRefund() external; 21 | } 22 | -------------------------------------------------------------------------------- /math/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | /** 4 | * @title SafeMath 5 | * @dev Math operations with safety checks that throw on error 6 | */ 7 | contract SafeMath { 8 | /** 9 | * @dev constructor 10 | */ 11 | function SafeMath() public { 12 | } 13 | 14 | function safeMul(uint256 a, uint256 b) internal pure returns (uint256) { 15 | uint256 c = a * b; 16 | assert(a == 0 || c / a == b); 17 | return c; 18 | } 19 | 20 | function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) { 21 | uint256 c = a / b; 22 | return c; 23 | } 24 | 25 | function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { 26 | assert(a >= b); 27 | return a - b; 28 | } 29 | 30 | function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { 31 | uint256 c = a + b; 32 | assert(c >= a); 33 | return c; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /token/IERC20Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | 4 | /** 5 | * @title IERC20Token - ERC20 interface 6 | * @dev see https://github.com/ethereum/EIPs/issues/20 7 | */ 8 | contract IERC20Token { 9 | string public name; 10 | string public symbol; 11 | uint8 public decimals; 12 | uint256 public totalSupply; 13 | 14 | function balanceOf(address _owner) public constant returns (uint256 balance); 15 | function transfer(address _to, uint256 _value) public returns (bool success); 16 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success); 17 | function approve(address _spender, uint256 _value) public returns (bool success); 18 | function allowance(address _owner, address _spender) public constant returns (uint256 remaining); 19 | 20 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 21 | event Approval(address indexed _owner, address indexed _spender, uint256 _value); 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 The Abyss LTD 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. -------------------------------------------------------------------------------- /Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './ownership/Ownable.sol'; 4 | 5 | 6 | /** 7 | * @title Pausable 8 | * @dev Base contract which allows children to implement an emergency stop mechanism. 9 | */ 10 | contract Pausable is Ownable { 11 | event Pause(); 12 | event Unpause(); 13 | 14 | bool public paused = false; 15 | 16 | 17 | /** 18 | * @dev Modifier to make a function callable only when the contract is not paused. 19 | */ 20 | modifier whenNotPaused() { 21 | require(!paused); 22 | _; 23 | } 24 | 25 | /** 26 | * @dev Modifier to make a function callable only when the contract is paused. 27 | */ 28 | modifier whenPaused() { 29 | require(paused); 30 | _; 31 | } 32 | 33 | /** 34 | * @dev called by the owner to pause, triggers stopped state 35 | */ 36 | function pause() onlyOwner whenNotPaused public { 37 | paused = true; 38 | Pause(); 39 | } 40 | 41 | /** 42 | * @dev called by the owner to unpause, returns to normal state 43 | */ 44 | function unpause() onlyOwner whenPaused public { 45 | paused = false; 46 | Unpause(); 47 | } 48 | } -------------------------------------------------------------------------------- /fund/ICrowdsaleReservationFund.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | /** 4 | * @title ICrowdsaleReservationFund 5 | * @dev ReservationFund methods used by crowdsale contract 6 | */ 7 | interface ICrowdsaleReservationFund { 8 | /** 9 | * @dev Check if contributor has transactions 10 | */ 11 | function canCompleteContribution(address contributor) external returns(bool); 12 | /** 13 | * @dev Complete contribution 14 | * @param contributor Contributor`s address 15 | */ 16 | function completeContribution(address contributor) external; 17 | /** 18 | * @dev Function accepts user`s contributed ether and amount of tokens to issue 19 | * @param contributor Contributor wallet address. 20 | * @param _tokensToIssue Token amount to issue 21 | * @param _bonusTokensToIssue Bonus token amount to issue 22 | */ 23 | function processContribution(address contributor, uint256 _tokensToIssue, uint256 _bonusTokensToIssue) external payable; 24 | 25 | /** 26 | * @dev Function returns current user`s contributed ether amount 27 | */ 28 | function contributionsOf(address contributor) external returns(uint256); 29 | 30 | /** 31 | * @dev Function is called on the end of successful crowdsale 32 | */ 33 | function onCrowdsaleEnd() external; 34 | } 35 | -------------------------------------------------------------------------------- /ownership/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | /** 4 | * @title Ownable 5 | * @dev The Ownable contract has an owner address, and provides basic authorization control 6 | * functions, this simplifies the implementation of "user permissions". 7 | */ 8 | contract Ownable { 9 | address public owner; 10 | address public newOwner; 11 | 12 | event OwnershipTransferred(address previousOwner, address newOwner); 13 | 14 | /** 15 | * @dev The Ownable constructor sets the original `owner` of the contract. 16 | */ 17 | function Ownable(address _owner) public { 18 | owner = _owner == address(0) ? msg.sender : _owner; 19 | } 20 | 21 | /** 22 | * @dev Throws if called by any account other than the owner. 23 | */ 24 | modifier onlyOwner() { 25 | require(msg.sender == owner); 26 | _; 27 | } 28 | 29 | /** 30 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 31 | * @param _newOwner The address to transfer ownership to. 32 | */ 33 | function transferOwnership(address _newOwner) public onlyOwner { 34 | require(_newOwner != owner); 35 | newOwner = _newOwner; 36 | } 37 | 38 | /** 39 | * @dev confirm ownership by a new owner 40 | */ 41 | function confirmOwnership() public { 42 | require(msg.sender == newOwner); 43 | OwnershipTransferred(owner, newOwner); 44 | owner = newOwner; 45 | newOwner = 0x0; 46 | } 47 | } -------------------------------------------------------------------------------- /ownership/MultiOwnable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | 4 | /** 5 | * @title MultiOwnable 6 | * @dev The MultiOwnable contract has owners addresses and provides basic authorization control 7 | * functions, this simplifies the implementation of "users permissions". 8 | */ 9 | contract MultiOwnable { 10 | address public manager; // address used to set owners 11 | address[] public owners; 12 | mapping(address => bool) public ownerByAddress; 13 | 14 | event SetOwners(address[] owners); 15 | 16 | modifier onlyOwner() { 17 | require(ownerByAddress[msg.sender] == true); 18 | _; 19 | } 20 | 21 | /** 22 | * @dev MultiOwnable constructor sets the manager 23 | */ 24 | function MultiOwnable() public { 25 | manager = msg.sender; 26 | } 27 | 28 | /** 29 | * @dev Function to set owners addresses 30 | */ 31 | function setOwners(address[] _owners) public { 32 | require(msg.sender == manager); 33 | _setOwners(_owners); 34 | 35 | } 36 | 37 | function _setOwners(address[] _owners) internal { 38 | for(uint256 i = 0; i < owners.length; i++) { 39 | ownerByAddress[owners[i]] = false; 40 | } 41 | 42 | 43 | for(uint256 j = 0; j < _owners.length; j++) { 44 | ownerByAddress[_owners[j]] = true; 45 | } 46 | owners = _owners; 47 | SetOwners(_owners); 48 | } 49 | 50 | function getOwners() public constant returns (address[]) { 51 | return owners; 52 | } 53 | } -------------------------------------------------------------------------------- /TapPoll.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './poll/BasePoll.sol'; 4 | import './fund/IPollManagedFund.sol'; 5 | 6 | 7 | /** 8 | * @title TapPoll 9 | * @dev Poll to increase tap amount 10 | */ 11 | contract TapPoll is BasePoll { 12 | uint256 public tap; 13 | uint256 public minTokensPerc = 0; 14 | 15 | /** 16 | * TapPoll constructor 17 | * @param _tap New tap value 18 | * @param _tokenAddress ERC20 compatible token contract address 19 | * @param _fundAddress Fund contract address 20 | * @param _startTime Poll start time 21 | * @param _endTime Poll end time 22 | * @param _minTokensPerc - Min percent of tokens from totalSupply where poll is considered to be fulfilled 23 | */ 24 | function TapPoll( 25 | uint256 _tap, 26 | address _tokenAddress, 27 | address _fundAddress, 28 | uint256 _startTime, 29 | uint256 _endTime, 30 | uint256 _minTokensPerc 31 | ) public 32 | BasePoll(_tokenAddress, _fundAddress, _startTime, _endTime, false) 33 | { 34 | tap = _tap; 35 | minTokensPerc = _minTokensPerc; 36 | } 37 | 38 | function onPollFinish(bool agree) internal { 39 | IPollManagedFund fund = IPollManagedFund(fundAddress); 40 | fund.onTapPollFinish(agree, tap); 41 | } 42 | 43 | function getVotedTokensPerc() public view returns(uint256) { 44 | return safeDiv(safeMul(safeAdd(yesCounter, noCounter), 100), token.totalSupply()); 45 | } 46 | 47 | function isSubjectApproved() internal view returns(bool) { 48 | return yesCounter > noCounter && getVotedTokensPerc() >= minTokensPerc; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RefundPoll.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './poll/BasePoll.sol'; 4 | import './fund/IPollManagedFund.sol'; 5 | 6 | /** 7 | * @title RefundPoll 8 | * @dev Enables fund refund mode 9 | */ 10 | contract RefundPoll is BasePoll { 11 | uint256 public holdEndTime = 0; 12 | 13 | /** 14 | * RefundPoll constructor 15 | * @param _tokenAddress ERC20 compatible token contract address 16 | * @param _fundAddress Fund contract address 17 | * @param _startTime Poll start time 18 | * @param _endTime Poll end time 19 | */ 20 | function RefundPoll( 21 | address _tokenAddress, 22 | address _fundAddress, 23 | uint256 _startTime, 24 | uint256 _endTime, 25 | uint256 _holdEndTime, 26 | bool _checkTransfersAfterEnd 27 | ) public 28 | BasePoll(_tokenAddress, _fundAddress, _startTime, _endTime, _checkTransfersAfterEnd) 29 | { 30 | holdEndTime = _holdEndTime; 31 | } 32 | 33 | function tryToFinalize() public returns(bool) { 34 | if(holdEndTime > 0 && holdEndTime > endTime) { 35 | require(now >= holdEndTime); 36 | } else { 37 | require(now >= endTime); 38 | } 39 | 40 | finalized = true; 41 | onPollFinish(isSubjectApproved()); 42 | return true; 43 | } 44 | 45 | function isSubjectApproved() internal view returns(bool) { 46 | return yesCounter > noCounter && yesCounter >= safeDiv(token.totalSupply(), 3); 47 | } 48 | 49 | function onPollFinish(bool agree) internal { 50 | IPollManagedFund fund = IPollManagedFund(fundAddress); 51 | fund.onRefundPollFinish(agree); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /token/ERC20Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './IERC20Token.sol'; 4 | import '../math/SafeMath.sol'; 5 | 6 | 7 | /** 8 | * @title ERC20Token - ERC20 base implementation 9 | * @dev see https://github.com/ethereum/EIPs/issues/20 10 | */ 11 | contract ERC20Token is IERC20Token, SafeMath { 12 | mapping (address => uint256) public balances; 13 | mapping (address => mapping (address => uint256)) public allowed; 14 | 15 | function transfer(address _to, uint256 _value) public returns (bool) { 16 | require(_to != address(0)); 17 | require(balances[msg.sender] >= _value); 18 | 19 | balances[msg.sender] = safeSub(balances[msg.sender], _value); 20 | balances[_to] = safeAdd(balances[_to], _value); 21 | Transfer(msg.sender, _to, _value); 22 | return true; 23 | } 24 | 25 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { 26 | require(_to != address(0)); 27 | require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value); 28 | 29 | balances[_to] = safeAdd(balances[_to], _value); 30 | balances[_from] = safeSub(balances[_from], _value); 31 | allowed[_from][msg.sender] = safeSub(allowed[_from][msg.sender], _value); 32 | Transfer(_from, _to, _value); 33 | return true; 34 | } 35 | 36 | function balanceOf(address _owner) public constant returns (uint256) { 37 | return balances[_owner]; 38 | } 39 | 40 | function approve(address _spender, uint256 _value) public returns (bool) { 41 | allowed[msg.sender][_spender] = _value; 42 | Approval(msg.sender, _spender, _value); 43 | return true; 44 | } 45 | 46 | function allowance(address _owner, address _spender) public constant returns (uint256) { 47 | return allowed[_owner][_spender]; 48 | } 49 | } -------------------------------------------------------------------------------- /token/LockedTokens.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import '../math/SafeMath.sol'; 4 | import './IERC20Token.sol'; 5 | 6 | 7 | /** 8 | * @title LockedTokens 9 | * @dev Lock tokens for certain period of time 10 | */ 11 | contract LockedTokens is SafeMath { 12 | struct Tokens { 13 | uint256 amount; 14 | uint256 lockEndTime; 15 | bool released; 16 | } 17 | 18 | event TokensUnlocked(address _to, uint256 _value); 19 | 20 | IERC20Token public token; 21 | address public crowdsaleAddress; 22 | mapping(address => Tokens[]) public walletTokens; 23 | 24 | /** 25 | * @dev LockedTokens constructor 26 | * @param _token ERC20 compatible token contract 27 | * @param _crowdsaleAddress Crowdsale contract address 28 | */ 29 | function LockedTokens(IERC20Token _token, address _crowdsaleAddress) public { 30 | token = _token; 31 | crowdsaleAddress = _crowdsaleAddress; 32 | } 33 | 34 | /** 35 | * @dev Functions locks tokens 36 | * @param _to Wallet address to transfer tokens after _lockEndTime 37 | * @param _amount Amount of tokens to lock 38 | * @param _lockEndTime End of lock period 39 | */ 40 | function addTokens(address _to, uint256 _amount, uint256 _lockEndTime) external { 41 | require(msg.sender == crowdsaleAddress); 42 | walletTokens[_to].push(Tokens({amount: _amount, lockEndTime: _lockEndTime, released: false})); 43 | } 44 | 45 | /** 46 | * @dev Called by owner of locked tokens to release them 47 | */ 48 | function releaseTokens() public { 49 | require(walletTokens[msg.sender].length > 0); 50 | 51 | for(uint256 i = 0; i < walletTokens[msg.sender].length; i++) { 52 | if(!walletTokens[msg.sender][i].released && now >= walletTokens[msg.sender][i].lockEndTime) { 53 | walletTokens[msg.sender][i].released = true; 54 | token.transfer(msg.sender, walletTokens[msg.sender][i].amount); 55 | TokensUnlocked(msg.sender, walletTokens[msg.sender][i].amount); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /token/TransferLimitedToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './ManagedToken.sol'; 4 | 5 | /** 6 | * @title TransferLimitedToken 7 | * @dev Token with ability to limit transfers within wallets included in limitedWallets list for certain period of time 8 | */ 9 | contract TransferLimitedToken is ManagedToken { 10 | uint256 public constant LIMIT_TRANSFERS_PERIOD = 365 days; 11 | 12 | mapping(address => bool) public limitedWallets; 13 | uint256 public limitEndDate; 14 | address public limitedWalletsManager; 15 | bool public isLimitEnabled; 16 | 17 | event TransfersEnabled(); 18 | 19 | modifier onlyManager() { 20 | require(msg.sender == limitedWalletsManager); 21 | _; 22 | } 23 | 24 | /** 25 | * @dev Check if transfer between addresses is available 26 | * @param _from From address 27 | * @param _to To address 28 | */ 29 | modifier canTransfer(address _from, address _to) { 30 | require(now >= limitEndDate || !isLimitEnabled || (!limitedWallets[_from] && !limitedWallets[_to])); 31 | _; 32 | } 33 | 34 | /** 35 | * @dev TransferLimitedToken constructor 36 | * @param _limitStartDate Limit start date 37 | * @param _listener Token listener(address can be 0x0) 38 | * @param _owners Owners list 39 | * @param _limitedWalletsManager Address used to add/del wallets from limitedWallets 40 | */ 41 | function TransferLimitedToken( 42 | uint256 _limitStartDate, 43 | address _listener, 44 | address[] _owners, 45 | address _limitedWalletsManager 46 | ) public ManagedToken(_listener, _owners) 47 | { 48 | limitEndDate = _limitStartDate + LIMIT_TRANSFERS_PERIOD; 49 | isLimitEnabled = true; 50 | limitedWalletsManager = _limitedWalletsManager; 51 | } 52 | 53 | /** 54 | * @dev Enable token transfers 55 | */ 56 | function enableTransfers() public { 57 | require(msg.sender == limitedWalletsManager); 58 | allowTransfers = true; 59 | TransfersEnabled(); 60 | } 61 | 62 | /** 63 | * @dev Add address to limitedWallets 64 | * @dev Can be called only by manager 65 | */ 66 | function addLimitedWalletAddress(address _wallet) public { 67 | require(msg.sender == limitedWalletsManager || ownerByAddress[msg.sender]); 68 | limitedWallets[_wallet] = true; 69 | } 70 | 71 | /** 72 | * @dev Del address from limitedWallets 73 | * @dev Can be called only by manager 74 | */ 75 | function delLimitedWalletAddress(address _wallet) public onlyManager { 76 | limitedWallets[_wallet] = false; 77 | } 78 | 79 | /** 80 | * @dev Disable transfer limit manually. Can be called only by manager 81 | */ 82 | function disableLimit() public onlyManager { 83 | isLimitEnabled = false; 84 | } 85 | 86 | function transfer(address _to, uint256 _value) public canTransfer(msg.sender, _to) returns (bool) { 87 | return super.transfer(_to, _value); 88 | } 89 | 90 | function transferFrom(address _from, address _to, uint256 _value) public canTransfer(_from, _to) returns (bool) { 91 | return super.transferFrom(_from, _to, _value); 92 | } 93 | 94 | function approve(address _spender, uint256 _value) public canTransfer(msg.sender, _spender) returns (bool) { 95 | return super.approve(_spender,_value); 96 | } 97 | } -------------------------------------------------------------------------------- /ReservationFund.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './fund/ICrowdsaleReservationFund.sol'; 4 | import './ISimpleCrowdsale.sol'; 5 | import './ownership/Ownable.sol'; 6 | import './math/SafeMath.sol'; 7 | 8 | 9 | contract ReservationFund is ICrowdsaleReservationFund, Ownable, SafeMath { 10 | bool public crowdsaleFinished = false; 11 | 12 | mapping(address => uint256) contributions; 13 | mapping(address => uint256) tokensToIssue; 14 | mapping(address => uint256) bonusTokensToIssue; 15 | 16 | ISimpleCrowdsale public crowdsale; 17 | 18 | event RefundPayment(address contributor, uint256 etherAmount); 19 | event TransferToFund(address contributor, uint256 etherAmount); 20 | event FinishCrowdsale(); 21 | 22 | function ReservationFund(address _owner) public Ownable(_owner) { 23 | } 24 | 25 | modifier onlyCrowdsale() { 26 | require(msg.sender == address(crowdsale)); 27 | _; 28 | } 29 | 30 | function setCrowdsaleAddress(address crowdsaleAddress) public onlyOwner { 31 | require(crowdsale == address(0)); 32 | crowdsale = ISimpleCrowdsale(crowdsaleAddress); 33 | } 34 | 35 | function onCrowdsaleEnd() external onlyCrowdsale { 36 | crowdsaleFinished = true; 37 | FinishCrowdsale(); 38 | } 39 | 40 | 41 | function canCompleteContribution(address contributor) external returns(bool) { 42 | if(crowdsaleFinished) { 43 | return false; 44 | } 45 | if(!crowdsale.isContributorInLists(contributor)) { 46 | return false; 47 | } 48 | if(contributions[contributor] == 0) { 49 | return false; 50 | } 51 | return true; 52 | } 53 | 54 | /** 55 | * @dev Function to check contributions by address 56 | */ 57 | function contributionsOf(address contributor) external returns(uint256) { 58 | return contributions[contributor]; 59 | } 60 | 61 | /** 62 | * @dev Process crowdsale contribution without whitelist 63 | */ 64 | function processContribution( 65 | address contributor, 66 | uint256 _tokensToIssue, 67 | uint256 _bonusTokensToIssue 68 | ) external payable onlyCrowdsale { 69 | contributions[contributor] = safeAdd(contributions[contributor], msg.value); 70 | tokensToIssue[contributor] = safeAdd(tokensToIssue[contributor], _tokensToIssue); 71 | bonusTokensToIssue[contributor] = safeAdd(bonusTokensToIssue[contributor], _bonusTokensToIssue); 72 | } 73 | 74 | /** 75 | * @dev Complete contribution after if user is whitelisted 76 | */ 77 | function completeContribution(address contributor) external { 78 | require(!crowdsaleFinished); 79 | require(crowdsale.isContributorInLists(contributor)); 80 | require(contributions[contributor] > 0); 81 | 82 | uint256 etherAmount = contributions[contributor]; 83 | uint256 tokenAmount = tokensToIssue[contributor]; 84 | uint256 tokenBonusAmount = bonusTokensToIssue[contributor]; 85 | 86 | contributions[contributor] = 0; 87 | tokensToIssue[contributor] = 0; 88 | bonusTokensToIssue[contributor] = 0; 89 | 90 | crowdsale.processReservationFundContribution.value(etherAmount)(contributor, tokenAmount, tokenBonusAmount); 91 | TransferToFund(contributor, etherAmount); 92 | } 93 | 94 | /** 95 | * @dev Refund payments if crowdsale is finalized 96 | */ 97 | function refundPayment(address contributor) public { 98 | require(crowdsaleFinished); 99 | require(contributions[contributor] > 0 || tokensToIssue[contributor] > 0); 100 | uint256 amountToRefund = contributions[contributor]; 101 | 102 | contributions[contributor] = 0; 103 | tokensToIssue[contributor] = 0; 104 | bonusTokensToIssue[contributor] = 0; 105 | 106 | contributor.transfer(amountToRefund); 107 | RefundPayment(contributor, amountToRefund); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /poll/BasePoll.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import '../math/SafeMath.sol'; 4 | import '../token/IERC20Token.sol'; 5 | 6 | 7 | /** 8 | * @title BasePoll 9 | * @dev Abstract base class for polling contracts 10 | */ 11 | contract BasePoll is SafeMath { 12 | struct Vote { 13 | uint256 time; 14 | uint256 weight; 15 | bool agree; 16 | } 17 | 18 | uint256 public constant MAX_TOKENS_WEIGHT_DENOM = 1000; 19 | 20 | IERC20Token public token; 21 | address public fundAddress; 22 | 23 | uint256 public startTime; 24 | uint256 public endTime; 25 | bool checkTransfersAfterEnd; 26 | 27 | uint256 public yesCounter = 0; 28 | uint256 public noCounter = 0; 29 | uint256 public totalVoted = 0; 30 | 31 | bool public finalized; 32 | mapping(address => Vote) public votesByAddress; 33 | 34 | modifier checkTime() { 35 | require(now >= startTime && now <= endTime); 36 | _; 37 | } 38 | 39 | modifier notFinalized() { 40 | require(!finalized); 41 | _; 42 | } 43 | 44 | /** 45 | * @dev BasePoll constructor 46 | * @param _tokenAddress ERC20 compatible token contract address 47 | * @param _fundAddress Fund contract address 48 | * @param _startTime Poll start time 49 | * @param _endTime Poll end time 50 | */ 51 | function BasePoll(address _tokenAddress, address _fundAddress, uint256 _startTime, uint256 _endTime, bool _checkTransfersAfterEnd) public { 52 | require(_tokenAddress != address(0)); 53 | require(_startTime >= now && _endTime > _startTime); 54 | 55 | token = IERC20Token(_tokenAddress); 56 | fundAddress = _fundAddress; 57 | startTime = _startTime; 58 | endTime = _endTime; 59 | finalized = false; 60 | checkTransfersAfterEnd = _checkTransfersAfterEnd; 61 | } 62 | 63 | /** 64 | * @dev Process user`s vote 65 | * @param agree True if user endorses the proposal else False 66 | */ 67 | function vote(bool agree) public checkTime { 68 | require(votesByAddress[msg.sender].time == 0); 69 | 70 | uint256 voiceWeight = token.balanceOf(msg.sender); 71 | uint256 maxVoiceWeight = safeDiv(token.totalSupply(), MAX_TOKENS_WEIGHT_DENOM); 72 | voiceWeight = voiceWeight <= maxVoiceWeight ? voiceWeight : maxVoiceWeight; 73 | 74 | if(agree) { 75 | yesCounter = safeAdd(yesCounter, voiceWeight); 76 | } else { 77 | noCounter = safeAdd(noCounter, voiceWeight); 78 | 79 | } 80 | 81 | votesByAddress[msg.sender].time = now; 82 | votesByAddress[msg.sender].weight = voiceWeight; 83 | votesByAddress[msg.sender].agree = agree; 84 | 85 | totalVoted = safeAdd(totalVoted, 1); 86 | } 87 | 88 | /** 89 | * @dev Revoke user`s vote 90 | */ 91 | function revokeVote() public checkTime { 92 | require(votesByAddress[msg.sender].time > 0); 93 | 94 | uint256 voiceWeight = votesByAddress[msg.sender].weight; 95 | bool agree = votesByAddress[msg.sender].agree; 96 | 97 | votesByAddress[msg.sender].time = 0; 98 | votesByAddress[msg.sender].weight = 0; 99 | votesByAddress[msg.sender].agree = false; 100 | 101 | totalVoted = safeSub(totalVoted, 1); 102 | if(agree) { 103 | yesCounter = safeSub(yesCounter, voiceWeight); 104 | } else { 105 | noCounter = safeSub(noCounter, voiceWeight); 106 | } 107 | } 108 | 109 | /** 110 | * @dev Function is called after token transfer from user`s wallet to check and correct user`s vote 111 | * 112 | */ 113 | function onTokenTransfer(address tokenHolder, uint256 amount) public { 114 | require(msg.sender == fundAddress); 115 | if(votesByAddress[tokenHolder].time == 0) { 116 | return; 117 | } 118 | if(!checkTransfersAfterEnd) { 119 | if(finalized || (now < startTime || now > endTime)) { 120 | return; 121 | } 122 | } 123 | 124 | if(token.balanceOf(tokenHolder) >= votesByAddress[tokenHolder].weight) { 125 | return; 126 | } 127 | uint256 voiceWeight = amount; 128 | if(amount > votesByAddress[tokenHolder].weight) { 129 | voiceWeight = votesByAddress[tokenHolder].weight; 130 | } 131 | 132 | if(votesByAddress[tokenHolder].agree) { 133 | yesCounter = safeSub(yesCounter, voiceWeight); 134 | } else { 135 | noCounter = safeSub(noCounter, voiceWeight); 136 | } 137 | votesByAddress[tokenHolder].weight = safeSub(votesByAddress[tokenHolder].weight, voiceWeight); 138 | } 139 | 140 | /** 141 | * Finalize poll and call onPollFinish callback with result 142 | */ 143 | function tryToFinalize() public notFinalized returns(bool) { 144 | if(now < endTime) { 145 | return false; 146 | } 147 | finalized = true; 148 | onPollFinish(isSubjectApproved()); 149 | return true; 150 | } 151 | 152 | function isNowApproved() public view returns(bool) { 153 | return isSubjectApproved(); 154 | } 155 | 156 | function isSubjectApproved() internal view returns(bool) { 157 | return yesCounter > noCounter; 158 | } 159 | 160 | /** 161 | * @dev callback called after poll finalization 162 | */ 163 | function onPollFinish(bool agree) internal; 164 | } -------------------------------------------------------------------------------- /token/ManagedToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import '../ownership/MultiOwnable.sol'; 4 | import './ERC20Token.sol'; 5 | import './ITokenEventListener.sol'; 6 | 7 | /** 8 | * @title ManagedToken 9 | * @dev ERC20 compatible token with issue and destroy facilities 10 | * @dev All transfers can be monitored by token event listener 11 | */ 12 | contract ManagedToken is ERC20Token, MultiOwnable { 13 | bool public allowTransfers = false; 14 | bool public issuanceFinished = false; 15 | 16 | ITokenEventListener public eventListener; 17 | 18 | event AllowTransfersChanged(bool _newState); 19 | event Issue(address indexed _to, uint256 _value); 20 | event Destroy(address indexed _from, uint256 _value); 21 | event IssuanceFinished(); 22 | 23 | modifier transfersAllowed() { 24 | require(allowTransfers); 25 | _; 26 | } 27 | 28 | modifier canIssue() { 29 | require(!issuanceFinished); 30 | _; 31 | } 32 | 33 | /** 34 | * @dev ManagedToken constructor 35 | * @param _listener Token listener(address can be 0x0) 36 | * @param _owners Owners list 37 | */ 38 | function ManagedToken(address _listener, address[] _owners) public { 39 | if(_listener != address(0)) { 40 | eventListener = ITokenEventListener(_listener); 41 | } 42 | _setOwners(_owners); 43 | } 44 | 45 | /** 46 | * @dev Enable/disable token transfers. Can be called only by owners 47 | * @param _allowTransfers True - allow False - disable 48 | */ 49 | function setAllowTransfers(bool _allowTransfers) external onlyOwner { 50 | allowTransfers = _allowTransfers; 51 | AllowTransfersChanged(_allowTransfers); 52 | } 53 | 54 | /** 55 | * @dev Set/remove token event listener 56 | * @param _listener Listener address (Contract must implement ITokenEventListener interface) 57 | */ 58 | function setListener(address _listener) public onlyOwner { 59 | if(_listener != address(0)) { 60 | eventListener = ITokenEventListener(_listener); 61 | } else { 62 | delete eventListener; 63 | } 64 | } 65 | 66 | function transfer(address _to, uint256 _value) public transfersAllowed returns (bool) { 67 | bool success = super.transfer(_to, _value); 68 | if(hasListener() && success) { 69 | eventListener.onTokenTransfer(msg.sender, _to, _value); 70 | } 71 | return success; 72 | } 73 | 74 | function transferFrom(address _from, address _to, uint256 _value) public transfersAllowed returns (bool) { 75 | bool success = super.transferFrom(_from, _to, _value); 76 | if(hasListener() && success) { 77 | eventListener.onTokenTransfer(_from, _to, _value); 78 | } 79 | return success; 80 | } 81 | 82 | function hasListener() internal view returns(bool) { 83 | if(eventListener == address(0)) { 84 | return false; 85 | } 86 | return true; 87 | } 88 | 89 | /** 90 | * @dev Issue tokens to specified wallet 91 | * @param _to Wallet address 92 | * @param _value Amount of tokens 93 | */ 94 | function issue(address _to, uint256 _value) external onlyOwner canIssue { 95 | totalSupply = safeAdd(totalSupply, _value); 96 | balances[_to] = safeAdd(balances[_to], _value); 97 | Issue(_to, _value); 98 | Transfer(address(0), _to, _value); 99 | } 100 | 101 | /** 102 | * @dev Destroy tokens on specified address (Called by owner or token holder) 103 | * @dev Fund contract address must be in the list of owners to burn token during refund 104 | * @param _from Wallet address 105 | * @param _value Amount of tokens to destroy 106 | */ 107 | function destroy(address _from, uint256 _value) external { 108 | require(ownerByAddress[msg.sender] || msg.sender == _from); 109 | require(balances[_from] >= _value); 110 | totalSupply = safeSub(totalSupply, _value); 111 | balances[_from] = safeSub(balances[_from], _value); 112 | Transfer(_from, address(0), _value); 113 | Destroy(_from, _value); 114 | } 115 | 116 | /** 117 | * @dev Increase the amount of tokens that an owner allowed to a spender. 118 | * 119 | * approve should be called when allowed[_spender] == 0. To increment 120 | * allowed value is better to use this function to avoid 2 calls (and wait until 121 | * the first transaction is mined) 122 | * From OpenZeppelin StandardToken.sol 123 | * @param _spender The address which will spend the funds. 124 | * @param _addedValue The amount of tokens to increase the allowance by. 125 | */ 126 | function increaseApproval(address _spender, uint _addedValue) public returns (bool) { 127 | allowed[msg.sender][_spender] = safeAdd(allowed[msg.sender][_spender], _addedValue); 128 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 129 | return true; 130 | } 131 | 132 | /** 133 | * @dev Decrease the amount of tokens that an owner allowed to a spender. 134 | * 135 | * approve should be called when allowed[_spender] == 0. To decrement 136 | * allowed value is better to use this function to avoid 2 calls (and wait until 137 | * the first transaction is mined) 138 | * From OpenZeppelin StandardToken.sol 139 | * @param _spender The address which will spend the funds. 140 | * @param _subtractedValue The amount of tokens to decrease the allowance by. 141 | */ 142 | function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) { 143 | uint oldValue = allowed[msg.sender][_spender]; 144 | if (_subtractedValue > oldValue) { 145 | allowed[msg.sender][_spender] = 0; 146 | } else { 147 | allowed[msg.sender][_spender] = safeSub(oldValue, _subtractedValue); 148 | } 149 | Approval(msg.sender, _spender, allowed[msg.sender][_spender]); 150 | return true; 151 | } 152 | 153 | /** 154 | * @dev Finish token issuance 155 | * @return True if success 156 | */ 157 | function finishIssuance() public onlyOwner returns (bool) { 158 | issuanceFinished = true; 159 | IssuanceFinished(); 160 | return true; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /PollManagedFund.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './DateTime.sol'; 4 | import './Fund.sol'; 5 | import './TapPoll.sol'; 6 | import './RefundPoll.sol'; 7 | import './fund/IPollManagedFund.sol'; 8 | import './token/ITokenEventListener.sol'; 9 | 10 | /** 11 | * @title PollManagedFund 12 | * @dev Fund controlled by users 13 | */ 14 | contract PollManagedFund is Fund, DateTime, ITokenEventListener { 15 | uint256 public constant TAP_POLL_DURATION = 3 days; 16 | uint256 public constant REFUND_POLL_DURATION = 7 days; 17 | uint256 public constant MAX_VOTED_TOKEN_PERC = 10; 18 | 19 | TapPoll public tapPoll; 20 | RefundPoll public refundPoll; 21 | 22 | uint256 public minVotedTokensPerc = 0; 23 | uint256 public secondRefundPollDate = 0; 24 | bool public isWithdrawEnabled = true; 25 | 26 | uint256[] public refundPollDates = [ 27 | 1530403200, // 01.07.2018 28 | 1538352000, // 01.10.2018 29 | 1546300800, // 01.01.2019 30 | 1554076800, // 01.04.2019 31 | 1561939200, // 01.07.2019 32 | 1569888000, // 01.10.2019 33 | 1577836800, // 01.01.2020 34 | 1585699200 // 01.04.2020 35 | ]; 36 | 37 | modifier onlyTokenHolder() { 38 | require(token.balanceOf(msg.sender) > 0); 39 | _; 40 | } 41 | 42 | event TapPollCreated(); 43 | event TapPollFinished(bool approved, uint256 _tap); 44 | event RefundPollCreated(); 45 | event RefundPollFinished(bool approved); 46 | 47 | /** 48 | * @dev PollManagedFund constructor 49 | * params - see Fund constructor 50 | */ 51 | function PollManagedFund( 52 | address _teamWallet, 53 | address _referralTokenWallet, 54 | address _foundationTokenWallet, 55 | address _companyTokenWallet, 56 | address _reserveTokenWallet, 57 | address _bountyTokenWallet, 58 | address _advisorTokenWallet, 59 | address _refundManager, 60 | address[] _owners 61 | ) public 62 | Fund(_teamWallet, _referralTokenWallet, _foundationTokenWallet, _companyTokenWallet, _reserveTokenWallet, _bountyTokenWallet, _advisorTokenWallet, _refundManager, _owners) 63 | { 64 | } 65 | 66 | function canWithdraw() public returns(bool) { 67 | if( 68 | address(refundPoll) != address(0) && 69 | !refundPoll.finalized() && 70 | refundPoll.holdEndTime() > 0 && 71 | now >= refundPoll.holdEndTime() && 72 | refundPoll.isNowApproved() 73 | ) { 74 | return false; 75 | } 76 | return isWithdrawEnabled; 77 | } 78 | 79 | /** 80 | * @dev ITokenEventListener implementation. Notify active poll contracts about token transfers 81 | */ 82 | function onTokenTransfer(address _from, address /*_to*/, uint256 _value) external { 83 | require(msg.sender == address(token)); 84 | if(address(tapPoll) != address(0) && !tapPoll.finalized()) { 85 | tapPoll.onTokenTransfer(_from, _value); 86 | } 87 | if(address(refundPoll) != address(0) && !refundPoll.finalized()) { 88 | refundPoll.onTokenTransfer(_from, _value); 89 | } 90 | } 91 | 92 | /** 93 | * @dev Update minVotedTokensPerc value after tap poll. 94 | * Set new value == 50% from current voted tokens amount 95 | */ 96 | function updateMinVotedTokens(uint256 _minVotedTokensPerc) internal { 97 | uint256 newPerc = safeDiv(_minVotedTokensPerc, 2); 98 | if(newPerc > MAX_VOTED_TOKEN_PERC) { 99 | minVotedTokensPerc = MAX_VOTED_TOKEN_PERC; 100 | return; 101 | } 102 | minVotedTokensPerc = newPerc; 103 | } 104 | 105 | // Tap poll 106 | function createTapPoll(uint8 tapIncPerc) public onlyOwner { 107 | require(state == FundState.TeamWithdraw); 108 | require(tapPoll == address(0)); 109 | require(getDay(now) == 10); 110 | require(tapIncPerc <= 50); 111 | uint256 _tap = safeAdd(tap, safeDiv(safeMul(tap, tapIncPerc), 100)); 112 | uint256 startTime = now; 113 | uint256 endTime = startTime + TAP_POLL_DURATION; 114 | tapPoll = new TapPoll(_tap, token, this, startTime, endTime, minVotedTokensPerc); 115 | TapPollCreated(); 116 | } 117 | 118 | function onTapPollFinish(bool agree, uint256 _tap) external { 119 | require(msg.sender == address(tapPoll) && tapPoll.finalized()); 120 | if(agree) { 121 | tap = _tap; 122 | } 123 | updateMinVotedTokens(tapPoll.getVotedTokensPerc()); 124 | TapPollFinished(agree, _tap); 125 | delete tapPoll; 126 | } 127 | 128 | // Refund poll 129 | function checkRefundPollDate() internal view returns(bool) { 130 | if(secondRefundPollDate > 0 && now >= secondRefundPollDate && now <= safeAdd(secondRefundPollDate, 1 days)) { 131 | return true; 132 | } 133 | 134 | for(uint i; i < refundPollDates.length; i++) { 135 | if(now >= refundPollDates[i] && now <= safeAdd(refundPollDates[i], 1 days)) { 136 | return true; 137 | } 138 | } 139 | return false; 140 | } 141 | 142 | function createRefundPoll() public onlyTokenHolder { 143 | require(state == FundState.TeamWithdraw); 144 | require(address(refundPoll) == address(0)); 145 | require(checkRefundPollDate()); 146 | 147 | if(secondRefundPollDate > 0 && now > safeAdd(secondRefundPollDate, 1 days)) { 148 | secondRefundPollDate = 0; 149 | } 150 | 151 | uint256 startTime = now; 152 | uint256 endTime = startTime + REFUND_POLL_DURATION; 153 | bool isFirstRefund = secondRefundPollDate == 0; 154 | uint256 holdEndTime = 0; 155 | 156 | if(isFirstRefund) { 157 | holdEndTime = toTimestamp( 158 | getYear(startTime), 159 | getMonth(startTime) + 1, 160 | 1 161 | ); 162 | } 163 | refundPoll = new RefundPoll(token, this, startTime, endTime, holdEndTime, isFirstRefund); 164 | RefundPollCreated(); 165 | } 166 | 167 | function onRefundPollFinish(bool agree) external { 168 | require(msg.sender == address(refundPoll) && refundPoll.finalized()); 169 | if(agree) { 170 | if(secondRefundPollDate > 0) { 171 | enableRefund(); 172 | } else { 173 | uint256 startTime = refundPoll.startTime(); 174 | secondRefundPollDate = toTimestamp( 175 | getYear(startTime), 176 | getMonth(startTime) + 2, 177 | 1 178 | ); 179 | isWithdrawEnabled = false; 180 | } 181 | } else { 182 | secondRefundPollDate = 0; 183 | isWithdrawEnabled = true; 184 | } 185 | RefundPollFinished(agree); 186 | 187 | delete refundPoll; 188 | } 189 | 190 | function forceRefund() public { 191 | require(msg.sender == refundManager); 192 | enableRefund(); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /DateTime.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | contract DateTime { 4 | /* 5 | * Date and Time utilities for ethereum contracts 6 | * 7 | */ 8 | struct _DateTime { 9 | uint16 year; 10 | uint8 month; 11 | uint8 day; 12 | uint8 hour; 13 | uint8 minute; 14 | uint8 second; 15 | uint8 weekday; 16 | } 17 | 18 | uint constant DAY_IN_SECONDS = 86400; 19 | uint constant YEAR_IN_SECONDS = 31536000; 20 | uint constant LEAP_YEAR_IN_SECONDS = 31622400; 21 | 22 | uint constant HOUR_IN_SECONDS = 3600; 23 | uint constant MINUTE_IN_SECONDS = 60; 24 | 25 | uint16 constant ORIGIN_YEAR = 1970; 26 | 27 | function isLeapYear(uint16 year) public pure returns (bool) { 28 | if (year % 4 != 0) { 29 | return false; 30 | } 31 | if (year % 100 != 0) { 32 | return true; 33 | } 34 | if (year % 400 != 0) { 35 | return false; 36 | } 37 | return true; 38 | } 39 | 40 | function leapYearsBefore(uint year) public pure returns (uint) { 41 | year -= 1; 42 | return year / 4 - year / 100 + year / 400; 43 | } 44 | 45 | function getDaysInMonth(uint8 month, uint16 year) public pure returns (uint8) { 46 | if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { 47 | return 31; 48 | } 49 | else if (month == 4 || month == 6 || month == 9 || month == 11) { 50 | return 30; 51 | } 52 | else if (isLeapYear(year)) { 53 | return 29; 54 | } 55 | else { 56 | return 28; 57 | } 58 | } 59 | 60 | function parseTimestamp(uint timestamp) internal pure returns (_DateTime dt) { 61 | uint secondsAccountedFor = 0; 62 | uint buf; 63 | uint8 i; 64 | 65 | // Year 66 | dt.year = getYear(timestamp); 67 | buf = leapYearsBefore(dt.year) - leapYearsBefore(ORIGIN_YEAR); 68 | 69 | secondsAccountedFor += LEAP_YEAR_IN_SECONDS * buf; 70 | secondsAccountedFor += YEAR_IN_SECONDS * (dt.year - ORIGIN_YEAR - buf); 71 | 72 | // Month 73 | uint secondsInMonth; 74 | for (i = 1; i <= 12; i++) { 75 | secondsInMonth = DAY_IN_SECONDS * getDaysInMonth(i, dt.year); 76 | if (secondsInMonth + secondsAccountedFor > timestamp) { 77 | dt.month = i; 78 | break; 79 | } 80 | secondsAccountedFor += secondsInMonth; 81 | } 82 | 83 | // Day 84 | for (i = 1; i <= getDaysInMonth(dt.month, dt.year); i++) { 85 | if (DAY_IN_SECONDS + secondsAccountedFor > timestamp) { 86 | dt.day = i; 87 | break; 88 | } 89 | secondsAccountedFor += DAY_IN_SECONDS; 90 | } 91 | 92 | // Hour 93 | dt.hour = getHour(timestamp); 94 | 95 | // Minute 96 | dt.minute = getMinute(timestamp); 97 | 98 | // Second 99 | dt.second = getSecond(timestamp); 100 | 101 | // Day of week. 102 | dt.weekday = getWeekday(timestamp); 103 | } 104 | 105 | function getYear(uint timestamp) public pure returns (uint16) { 106 | uint secondsAccountedFor = 0; 107 | uint16 year; 108 | uint numLeapYears; 109 | 110 | // Year 111 | year = uint16(ORIGIN_YEAR + timestamp / YEAR_IN_SECONDS); 112 | numLeapYears = leapYearsBefore(year) - leapYearsBefore(ORIGIN_YEAR); 113 | 114 | secondsAccountedFor += LEAP_YEAR_IN_SECONDS * numLeapYears; 115 | secondsAccountedFor += YEAR_IN_SECONDS * (year - ORIGIN_YEAR - numLeapYears); 116 | 117 | while (secondsAccountedFor > timestamp) { 118 | if (isLeapYear(uint16(year - 1))) { 119 | secondsAccountedFor -= LEAP_YEAR_IN_SECONDS; 120 | } 121 | else { 122 | secondsAccountedFor -= YEAR_IN_SECONDS; 123 | } 124 | year -= 1; 125 | } 126 | return year; 127 | } 128 | 129 | function getMonth(uint timestamp) public pure returns (uint8) { 130 | return parseTimestamp(timestamp).month; 131 | } 132 | 133 | function getDay(uint timestamp) public pure returns (uint8) { 134 | return parseTimestamp(timestamp).day; 135 | } 136 | 137 | function getHour(uint timestamp) public pure returns (uint8) { 138 | return uint8((timestamp / 60 / 60) % 24); 139 | } 140 | 141 | function getMinute(uint timestamp) public pure returns (uint8) { 142 | return uint8((timestamp / 60) % 60); 143 | } 144 | 145 | function getSecond(uint timestamp) public pure returns (uint8) { 146 | return uint8(timestamp % 60); 147 | } 148 | 149 | function getWeekday(uint timestamp) public pure returns (uint8) { 150 | return uint8((timestamp / DAY_IN_SECONDS + 4) % 7); 151 | } 152 | 153 | function toTimestamp(uint16 year, uint8 month, uint8 day) public pure returns (uint timestamp) { 154 | return toTimestamp(year, month, day, 0, 0, 0); 155 | } 156 | 157 | function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour) public pure returns (uint timestamp) { 158 | return toTimestamp(year, month, day, hour, 0, 0); 159 | } 160 | 161 | function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute) public pure returns (uint timestamp) { 162 | return toTimestamp(year, month, day, hour, minute, 0); 163 | } 164 | 165 | function toTimestamp(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute, uint8 second) public pure returns (uint timestamp) { 166 | uint16 i; 167 | 168 | // Year 169 | for (i = ORIGIN_YEAR; i < year; i++) { 170 | if (isLeapYear(i)) { 171 | timestamp += LEAP_YEAR_IN_SECONDS; 172 | } 173 | else { 174 | timestamp += YEAR_IN_SECONDS; 175 | } 176 | } 177 | 178 | // Month 179 | uint8[12] memory monthDayCounts; 180 | monthDayCounts[0] = 31; 181 | if (isLeapYear(year)) { 182 | monthDayCounts[1] = 29; 183 | } 184 | else { 185 | monthDayCounts[1] = 28; 186 | } 187 | monthDayCounts[2] = 31; 188 | monthDayCounts[3] = 30; 189 | monthDayCounts[4] = 31; 190 | monthDayCounts[5] = 30; 191 | monthDayCounts[6] = 31; 192 | monthDayCounts[7] = 31; 193 | monthDayCounts[8] = 30; 194 | monthDayCounts[9] = 31; 195 | monthDayCounts[10] = 30; 196 | monthDayCounts[11] = 31; 197 | 198 | for (i = 1; i < month; i++) { 199 | timestamp += DAY_IN_SECONDS * monthDayCounts[i - 1]; 200 | } 201 | 202 | // Day 203 | timestamp += DAY_IN_SECONDS * (day - 1); 204 | 205 | // Hour 206 | timestamp += HOUR_IN_SECONDS * (hour); 207 | 208 | // Minute 209 | timestamp += MINUTE_IN_SECONDS * (minute); 210 | 211 | // Second 212 | timestamp += second; 213 | 214 | return timestamp; 215 | } 216 | } -------------------------------------------------------------------------------- /Fund.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './fund/ICrowdsaleFund.sol'; 4 | import './math/SafeMath.sol'; 5 | import './ownership/MultiOwnable.sol'; 6 | import './token/ManagedToken.sol'; 7 | import './ISimpleCrowdsale.sol'; 8 | 9 | 10 | contract Fund is ICrowdsaleFund, SafeMath, MultiOwnable { 11 | enum FundState { 12 | Crowdsale, 13 | CrowdsaleRefund, 14 | TeamWithdraw, 15 | Refund 16 | } 17 | 18 | FundState public state = FundState.Crowdsale; 19 | ManagedToken public token; 20 | 21 | uint256 public constant INITIAL_TAP = 192901234567901; // (wei/sec) == 500 ether/month 22 | 23 | address public teamWallet; 24 | uint256 public crowdsaleEndDate; 25 | 26 | address public referralTokenWallet; 27 | address public foundationTokenWallet; 28 | address public reserveTokenWallet; 29 | address public bountyTokenWallet; 30 | address public companyTokenWallet; 31 | address public advisorTokenWallet; 32 | address public lockedTokenAddress; 33 | address public refundManager; 34 | 35 | uint256 public tap; 36 | uint256 public lastWithdrawTime = 0; 37 | uint256 public firstWithdrawAmount = 0; 38 | 39 | address public crowdsaleAddress; 40 | mapping(address => uint256) public contributions; 41 | 42 | event RefundContributor(address tokenHolder, uint256 amountWei, uint256 timestamp); 43 | event RefundHolder(address tokenHolder, uint256 amountWei, uint256 tokenAmount, uint256 timestamp); 44 | event Withdraw(uint256 amountWei, uint256 timestamp); 45 | event RefundEnabled(address initiatorAddress); 46 | 47 | /** 48 | * @dev Fund constructor 49 | * @param _teamWallet Withdraw functions transfers ether to this address 50 | * @param _referralTokenWallet Referral wallet address 51 | * @param _companyTokenWallet Company wallet address 52 | * @param _reserveTokenWallet Reserve wallet address 53 | * @param _bountyTokenWallet Bounty wallet address 54 | * @param _advisorTokenWallet Advisor wallet address 55 | * @param _owners Contract owners 56 | */ 57 | function Fund( 58 | address _teamWallet, 59 | address _referralTokenWallet, 60 | address _foundationTokenWallet, 61 | address _companyTokenWallet, 62 | address _reserveTokenWallet, 63 | address _bountyTokenWallet, 64 | address _advisorTokenWallet, 65 | address _refundManager, 66 | address[] _owners 67 | ) public 68 | { 69 | teamWallet = _teamWallet; 70 | referralTokenWallet = _referralTokenWallet; 71 | foundationTokenWallet = _foundationTokenWallet; 72 | companyTokenWallet = _companyTokenWallet; 73 | reserveTokenWallet = _reserveTokenWallet; 74 | bountyTokenWallet = _bountyTokenWallet; 75 | advisorTokenWallet = _advisorTokenWallet; 76 | refundManager = _refundManager; 77 | _setOwners(_owners); 78 | } 79 | 80 | modifier withdrawEnabled() { 81 | require(canWithdraw()); 82 | _; 83 | } 84 | 85 | modifier onlyCrowdsale() { 86 | require(msg.sender == crowdsaleAddress); 87 | _; 88 | } 89 | 90 | function canWithdraw() public returns(bool); 91 | 92 | function setCrowdsaleAddress(address _crowdsaleAddress) public onlyOwner { 93 | require(crowdsaleAddress == address(0)); 94 | crowdsaleAddress = _crowdsaleAddress; 95 | } 96 | 97 | function setTokenAddress(address _tokenAddress) public onlyOwner { 98 | require(address(token) == address(0)); 99 | token = ManagedToken(_tokenAddress); 100 | } 101 | 102 | function setLockedTokenAddress(address _lockedTokenAddress) public onlyOwner { 103 | require(address(lockedTokenAddress) == address(0)); 104 | lockedTokenAddress = _lockedTokenAddress; 105 | } 106 | 107 | /** 108 | * @dev Process crowdsale contribution 109 | */ 110 | function processContribution(address contributor) external payable onlyCrowdsale { 111 | require(state == FundState.Crowdsale); 112 | uint256 totalContribution = safeAdd(contributions[contributor], msg.value); 113 | contributions[contributor] = totalContribution; 114 | } 115 | 116 | /** 117 | * @dev Callback is called after crowdsale finalization if soft cap is reached 118 | */ 119 | function onCrowdsaleEnd() external onlyCrowdsale { 120 | state = FundState.TeamWithdraw; 121 | ISimpleCrowdsale crowdsale = ISimpleCrowdsale(crowdsaleAddress); 122 | firstWithdrawAmount = safeDiv(crowdsale.getSoftCap(), 2); 123 | lastWithdrawTime = now; 124 | tap = INITIAL_TAP; 125 | crowdsaleEndDate = now; 126 | } 127 | 128 | /** 129 | * @dev Callback is called after crowdsale finalization if soft cap is not reached 130 | */ 131 | function enableCrowdsaleRefund() external onlyCrowdsale { 132 | require(state == FundState.Crowdsale); 133 | state = FundState.CrowdsaleRefund; 134 | } 135 | 136 | /** 137 | * @dev Function is called by contributor to refund payments if crowdsale failed to reach soft cap 138 | */ 139 | function refundCrowdsaleContributor() external { 140 | require(state == FundState.CrowdsaleRefund); 141 | require(contributions[msg.sender] > 0); 142 | 143 | uint256 refundAmount = contributions[msg.sender]; 144 | contributions[msg.sender] = 0; 145 | token.destroy(msg.sender, token.balanceOf(msg.sender)); 146 | msg.sender.transfer(refundAmount); 147 | RefundContributor(msg.sender, refundAmount, now); 148 | } 149 | 150 | /** 151 | * @dev Function is called by owner to refund payments if crowdsale failed to reach soft cap 152 | */ 153 | function autoRefundCrowdsaleContributor(address contributorAddress) external { 154 | require(ownerByAddress[msg.sender] == true || msg.sender == refundManager); 155 | require(state == FundState.CrowdsaleRefund); 156 | require(contributions[contributorAddress] > 0); 157 | 158 | uint256 refundAmount = contributions[contributorAddress]; 159 | contributions[contributorAddress] = 0; 160 | token.destroy(contributorAddress, token.balanceOf(contributorAddress)); 161 | contributorAddress.transfer(refundAmount); 162 | RefundContributor(contributorAddress, refundAmount, now); 163 | } 164 | 165 | /** 166 | * @dev Decrease tap amount 167 | * @param _tap New tap value 168 | */ 169 | function decTap(uint256 _tap) external onlyOwner { 170 | require(state == FundState.TeamWithdraw); 171 | require(_tap < tap); 172 | tap = _tap; 173 | } 174 | 175 | function getCurrentTapAmount() public constant returns(uint256) { 176 | if(state != FundState.TeamWithdraw) { 177 | return 0; 178 | } 179 | return calcTapAmount(); 180 | } 181 | 182 | function calcTapAmount() internal view returns(uint256) { 183 | uint256 amount = safeMul(safeSub(now, lastWithdrawTime), tap); 184 | if(address(this).balance < amount) { 185 | amount = address(this).balance; 186 | } 187 | return amount; 188 | } 189 | 190 | function firstWithdraw() public onlyOwner withdrawEnabled { 191 | require(firstWithdrawAmount > 0); 192 | uint256 amount = firstWithdrawAmount; 193 | firstWithdrawAmount = 0; 194 | teamWallet.transfer(amount); 195 | Withdraw(amount, now); 196 | } 197 | 198 | /** 199 | * @dev Withdraw tap amount 200 | */ 201 | function withdraw() public onlyOwner withdrawEnabled { 202 | require(state == FundState.TeamWithdraw); 203 | uint256 amount = calcTapAmount(); 204 | lastWithdrawTime = now; 205 | teamWallet.transfer(amount); 206 | Withdraw(amount, now); 207 | } 208 | 209 | // Refund 210 | /** 211 | * @dev Called to start refunding 212 | */ 213 | function enableRefund() internal { 214 | require(state == FundState.TeamWithdraw); 215 | state = FundState.Refund; 216 | token.destroy(lockedTokenAddress, token.balanceOf(lockedTokenAddress)); 217 | token.destroy(companyTokenWallet, token.balanceOf(companyTokenWallet)); 218 | token.destroy(reserveTokenWallet, token.balanceOf(reserveTokenWallet)); 219 | token.destroy(foundationTokenWallet, token.balanceOf(foundationTokenWallet)); 220 | token.destroy(bountyTokenWallet, token.balanceOf(bountyTokenWallet)); 221 | token.destroy(referralTokenWallet, token.balanceOf(referralTokenWallet)); 222 | token.destroy(advisorTokenWallet, token.balanceOf(advisorTokenWallet)); 223 | RefundEnabled(msg.sender); 224 | } 225 | 226 | /** 227 | * @dev Function is called by contributor to refund 228 | * Buy user tokens for refundTokenPrice and destroy them 229 | */ 230 | function refundTokenHolder() public { 231 | require(state == FundState.Refund); 232 | 233 | uint256 tokenBalance = token.balanceOf(msg.sender); 234 | require(tokenBalance > 0); 235 | uint256 refundAmount = safeDiv(safeMul(tokenBalance, address(this).balance), token.totalSupply()); 236 | require(refundAmount > 0); 237 | 238 | token.destroy(msg.sender, tokenBalance); 239 | msg.sender.transfer(refundAmount); 240 | 241 | RefundHolder(msg.sender, refundAmount, tokenBalance, now); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Crowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.21; 2 | 3 | import './fund/ICrowdsaleFund.sol'; 4 | import './fund/ICrowdsaleReservationFund.sol'; 5 | import './token/IERC20Token.sol'; 6 | import './token/TransferLimitedToken.sol'; 7 | import './token/LockedTokens.sol'; 8 | import './ownership/Ownable.sol'; 9 | import './Pausable.sol'; 10 | import './ISimpleCrowdsale.sol'; 11 | 12 | 13 | contract TheAbyssDAICO is Ownable, SafeMath, Pausable, ISimpleCrowdsale { 14 | enum AdditionalBonusState { 15 | Unavailable, 16 | Active, 17 | Applied 18 | } 19 | 20 | uint256 public constant ADDITIONAL_BONUS_NUM = 3; 21 | uint256 public constant ADDITIONAL_BONUS_DENOM = 100; 22 | 23 | uint256 public constant ETHER_MIN_CONTRIB = 0.2 ether; 24 | uint256 public constant ETHER_MAX_CONTRIB = 20 ether; 25 | 26 | uint256 public constant ETHER_MIN_CONTRIB_PRIVATE = 100 ether; 27 | uint256 public constant ETHER_MAX_CONTRIB_PRIVATE = 3000 ether; 28 | 29 | uint256 public constant ETHER_MIN_CONTRIB_USA = 0.2 ether; 30 | uint256 public constant ETHER_MAX_CONTRIB_USA = 20 ether; 31 | 32 | uint256 public constant SALE_START_TIME = 1524060000; // 18.04.2018 14:00:00 UTC 33 | uint256 public constant SALE_END_TIME = 1526479200; // 16.05.2018 14:00:00 UTC 34 | 35 | uint256 public constant BONUS_WINDOW_1_END_TIME = SALE_START_TIME + 2 days; 36 | uint256 public constant BONUS_WINDOW_2_END_TIME = SALE_START_TIME + 7 days; 37 | uint256 public constant BONUS_WINDOW_3_END_TIME = SALE_START_TIME + 14 days; 38 | uint256 public constant BONUS_WINDOW_4_END_TIME = SALE_START_TIME + 21 days; 39 | 40 | uint256 public constant MAX_CONTRIB_CHECK_END_TIME = SALE_START_TIME + 1 days; 41 | 42 | uint256 public constant BNB_TOKEN_PRICE_NUM = 169; 43 | uint256 public constant BNB_TOKEN_PRICE_DENOM = 1; 44 | 45 | uint256 public tokenPriceNum = 0; 46 | uint256 public tokenPriceDenom = 0; 47 | 48 | TransferLimitedToken public token; 49 | ICrowdsaleFund public fund; 50 | ICrowdsaleReservationFund public reservationFund; 51 | LockedTokens public lockedTokens; 52 | 53 | mapping(address => bool) public whiteList; 54 | mapping(address => bool) public privilegedList; 55 | mapping(address => AdditionalBonusState) public additionalBonusOwnerState; 56 | mapping(address => uint256) public userTotalContributed; 57 | 58 | address public bnbTokenWallet; 59 | address public referralTokenWallet; 60 | address public foundationTokenWallet; 61 | address public advisorsTokenWallet; 62 | address public companyTokenWallet; 63 | address public reserveTokenWallet; 64 | address public bountyTokenWallet; 65 | 66 | uint256 public totalEtherContributed = 0; 67 | uint256 public rawTokenSupply = 0; 68 | 69 | // BNB 70 | IERC20Token public bnbToken; 71 | uint256 public BNB_HARD_CAP = 300000 ether; // 300K BNB 72 | uint256 public BNB_MIN_CONTRIB = 1000 ether; // 1K BNB 73 | mapping(address => uint256) public bnbContributions; 74 | uint256 public totalBNBContributed = 0; 75 | bool public bnbWithdrawEnabled = false; 76 | 77 | uint256 public hardCap = 0; // World hard cap will be set right before Token Sale 78 | uint256 public softCap = 0; // World soft cap will be set right before Token Sale 79 | 80 | bool public bnbRefundEnabled = false; 81 | 82 | event LogContribution(address contributor, uint256 amountWei, uint256 tokenAmount, uint256 tokenBonus, bool additionalBonusApplied, uint256 timestamp); 83 | event ReservationFundContribution(address contributor, uint256 amountWei, uint256 tokensToIssue, uint256 bonusTokensToIssue, uint256 timestamp); 84 | event LogBNBContribution(address contributor, uint256 amountBNB, uint256 tokenAmount, uint256 tokenBonus, bool additionalBonusApplied, uint256 timestamp); 85 | 86 | modifier checkContribution() { 87 | require(isValidContribution()); 88 | _; 89 | } 90 | 91 | modifier checkBNBContribution() { 92 | require(isValidBNBContribution()); 93 | _; 94 | } 95 | 96 | modifier checkCap() { 97 | require(validateCap()); 98 | _; 99 | } 100 | 101 | modifier checkTime() { 102 | require(now >= SALE_START_TIME && now <= SALE_END_TIME); 103 | _; 104 | } 105 | 106 | function TheAbyssDAICO( 107 | address bnbTokenAddress, 108 | address tokenAddress, 109 | address fundAddress, 110 | address reservationFundAddress, 111 | address _bnbTokenWallet, 112 | address _referralTokenWallet, 113 | address _foundationTokenWallet, 114 | address _advisorsTokenWallet, 115 | address _companyTokenWallet, 116 | address _reserveTokenWallet, 117 | address _bountyTokenWallet, 118 | address _owner 119 | ) public 120 | Ownable(_owner) 121 | { 122 | require(tokenAddress != address(0)); 123 | 124 | bnbToken = IERC20Token(bnbTokenAddress); 125 | token = TransferLimitedToken(tokenAddress); 126 | fund = ICrowdsaleFund(fundAddress); 127 | reservationFund = ICrowdsaleReservationFund(reservationFundAddress); 128 | 129 | bnbTokenWallet = _bnbTokenWallet; 130 | referralTokenWallet = _referralTokenWallet; 131 | foundationTokenWallet = _foundationTokenWallet; 132 | advisorsTokenWallet = _advisorsTokenWallet; 133 | companyTokenWallet = _companyTokenWallet; 134 | reserveTokenWallet = _reserveTokenWallet; 135 | bountyTokenWallet = _bountyTokenWallet; 136 | } 137 | 138 | /** 139 | * @dev check if address can contribute 140 | */ 141 | function isContributorInLists(address contributor) external view returns(bool) { 142 | return whiteList[contributor] || privilegedList[contributor] || token.limitedWallets(contributor); 143 | } 144 | 145 | /** 146 | * @dev check contribution amount and time 147 | */ 148 | function isValidContribution() internal view returns(bool) { 149 | uint256 currentUserContribution = safeAdd(msg.value, userTotalContributed[msg.sender]); 150 | if(whiteList[msg.sender] && msg.value >= ETHER_MIN_CONTRIB) { 151 | if(now <= MAX_CONTRIB_CHECK_END_TIME && currentUserContribution > ETHER_MAX_CONTRIB ) { 152 | return false; 153 | } 154 | return true; 155 | 156 | } 157 | if(privilegedList[msg.sender] && msg.value >= ETHER_MIN_CONTRIB_PRIVATE) { 158 | if(now <= MAX_CONTRIB_CHECK_END_TIME && currentUserContribution > ETHER_MAX_CONTRIB_PRIVATE ) { 159 | return false; 160 | } 161 | return true; 162 | } 163 | 164 | if(token.limitedWallets(msg.sender) && msg.value >= ETHER_MIN_CONTRIB_USA) { 165 | if(now <= MAX_CONTRIB_CHECK_END_TIME && currentUserContribution > ETHER_MAX_CONTRIB_USA) { 166 | return false; 167 | } 168 | return true; 169 | } 170 | 171 | return false; 172 | } 173 | 174 | /** 175 | * @dev Check hard cap overflow 176 | */ 177 | function validateCap() internal view returns(bool){ 178 | if(msg.value <= safeSub(hardCap, totalEtherContributed)) { 179 | return true; 180 | } 181 | return false; 182 | } 183 | 184 | /** 185 | * @dev Set token price once before start of crowdsale 186 | */ 187 | function setTokenPrice(uint256 _tokenPriceNum, uint256 _tokenPriceDenom) public onlyOwner { 188 | require(tokenPriceNum == 0 && tokenPriceDenom == 0); 189 | require(_tokenPriceNum > 0 && _tokenPriceDenom > 0); 190 | tokenPriceNum = _tokenPriceNum; 191 | tokenPriceDenom = _tokenPriceDenom; 192 | } 193 | 194 | /** 195 | * @dev Set hard cap. 196 | * @param _hardCap - Hard cap value 197 | */ 198 | function setHardCap(uint256 _hardCap) public onlyOwner { 199 | require(hardCap == 0); 200 | hardCap = _hardCap; 201 | } 202 | 203 | /** 204 | * @dev Set soft cap. 205 | * @param _softCap - Soft cap value 206 | */ 207 | function setSoftCap(uint256 _softCap) public onlyOwner { 208 | require(softCap == 0); 209 | softCap = _softCap; 210 | } 211 | 212 | /** 213 | * @dev Get soft cap amount 214 | **/ 215 | function getSoftCap() external view returns(uint256) { 216 | return softCap; 217 | } 218 | 219 | /** 220 | * @dev Check bnb contribution time, amount and hard cap overflow 221 | */ 222 | function isValidBNBContribution() internal view returns(bool) { 223 | if(token.limitedWallets(msg.sender)) { 224 | return false; 225 | } 226 | if(!whiteList[msg.sender] && !privilegedList[msg.sender]) { 227 | return false; 228 | } 229 | uint256 amount = bnbToken.allowance(msg.sender, address(this)); 230 | if(amount < BNB_MIN_CONTRIB || safeAdd(totalBNBContributed, amount) > BNB_HARD_CAP) { 231 | return false; 232 | } 233 | return true; 234 | 235 | } 236 | 237 | /** 238 | * @dev Calc bonus amount by contribution time 239 | */ 240 | function getBonus() internal constant returns (uint256, uint256) { 241 | uint256 numerator = 0; 242 | uint256 denominator = 100; 243 | 244 | if(now < BONUS_WINDOW_1_END_TIME) { 245 | numerator = 25; 246 | } else if(now < BONUS_WINDOW_2_END_TIME) { 247 | numerator = 15; 248 | } else if(now < BONUS_WINDOW_3_END_TIME) { 249 | numerator = 10; 250 | } else if(now < BONUS_WINDOW_4_END_TIME) { 251 | numerator = 5; 252 | } else { 253 | numerator = 0; 254 | } 255 | 256 | return (numerator, denominator); 257 | } 258 | 259 | function addToLists( 260 | address _wallet, 261 | bool isInWhiteList, 262 | bool isInPrivilegedList, 263 | bool isInLimitedList, 264 | bool hasAdditionalBonus 265 | ) public onlyOwner { 266 | if(isInWhiteList) { 267 | whiteList[_wallet] = true; 268 | } 269 | if(isInPrivilegedList) { 270 | privilegedList[_wallet] = true; 271 | } 272 | if(isInLimitedList) { 273 | token.addLimitedWalletAddress(_wallet); 274 | } 275 | if(hasAdditionalBonus) { 276 | additionalBonusOwnerState[_wallet] = AdditionalBonusState.Active; 277 | } 278 | if(reservationFund.canCompleteContribution(_wallet)) { 279 | reservationFund.completeContribution(_wallet); 280 | } 281 | } 282 | 283 | /** 284 | * @dev Add wallet to whitelist. For contract owner only. 285 | */ 286 | function addToWhiteList(address _wallet) public onlyOwner { 287 | whiteList[_wallet] = true; 288 | } 289 | 290 | /** 291 | * @dev Add wallet to additional bonus members. For contract owner only. 292 | */ 293 | function addAdditionalBonusMember(address _wallet) public onlyOwner { 294 | additionalBonusOwnerState[_wallet] = AdditionalBonusState.Active; 295 | } 296 | 297 | /** 298 | * @dev Add wallet to privileged list. For contract owner only. 299 | */ 300 | function addToPrivilegedList(address _wallet) public onlyOwner { 301 | privilegedList[_wallet] = true; 302 | } 303 | 304 | /** 305 | * @dev Set LockedTokens contract address 306 | */ 307 | function setLockedTokens(address lockedTokensAddress) public onlyOwner { 308 | lockedTokens = LockedTokens(lockedTokensAddress); 309 | } 310 | 311 | /** 312 | * @dev Fallback function to receive ether contributions 313 | */ 314 | function () payable public whenNotPaused { 315 | if(whiteList[msg.sender] || privilegedList[msg.sender] || token.limitedWallets(msg.sender)) { 316 | processContribution(msg.sender, msg.value); 317 | } else { 318 | processReservationContribution(msg.sender, msg.value); 319 | } 320 | } 321 | 322 | function processReservationContribution(address contributor, uint256 amount) private checkTime checkCap { 323 | require(amount >= ETHER_MIN_CONTRIB); 324 | 325 | if(now <= MAX_CONTRIB_CHECK_END_TIME) { 326 | uint256 currentUserContribution = safeAdd(amount, reservationFund.contributionsOf(contributor)); 327 | require(currentUserContribution <= ETHER_MAX_CONTRIB); 328 | } 329 | uint256 bonusNum = 0; 330 | uint256 bonusDenom = 100; 331 | (bonusNum, bonusDenom) = getBonus(); 332 | uint256 tokenBonusAmount = 0; 333 | uint256 tokenAmount = safeDiv(safeMul(amount, tokenPriceNum), tokenPriceDenom); 334 | 335 | if(bonusNum > 0) { 336 | tokenBonusAmount = safeDiv(safeMul(tokenAmount, bonusNum), bonusDenom); 337 | } 338 | 339 | reservationFund.processContribution.value(amount)( 340 | contributor, 341 | tokenAmount, 342 | tokenBonusAmount 343 | ); 344 | ReservationFundContribution(contributor, amount, tokenAmount, tokenBonusAmount, now); 345 | } 346 | 347 | /** 348 | * @dev Process BNB token contribution 349 | * Transfer all amount of tokens approved by sender. Calc bonuses and issue tokens to contributor. 350 | */ 351 | function processBNBContribution() public whenNotPaused checkTime checkBNBContribution { 352 | bool additionalBonusApplied = false; 353 | uint256 bonusNum = 0; 354 | uint256 bonusDenom = 100; 355 | (bonusNum, bonusDenom) = getBonus(); 356 | uint256 amountBNB = bnbToken.allowance(msg.sender, address(this)); 357 | bnbToken.transferFrom(msg.sender, address(this), amountBNB); 358 | bnbContributions[msg.sender] = safeAdd(bnbContributions[msg.sender], amountBNB); 359 | 360 | uint256 tokenBonusAmount = 0; 361 | uint256 tokenAmount = safeDiv(safeMul(amountBNB, BNB_TOKEN_PRICE_NUM), BNB_TOKEN_PRICE_DENOM); 362 | rawTokenSupply = safeAdd(rawTokenSupply, tokenAmount); 363 | if(bonusNum > 0) { 364 | tokenBonusAmount = safeDiv(safeMul(tokenAmount, bonusNum), bonusDenom); 365 | } 366 | 367 | if(additionalBonusOwnerState[msg.sender] == AdditionalBonusState.Active) { 368 | additionalBonusOwnerState[msg.sender] = AdditionalBonusState.Applied; 369 | uint256 additionalBonus = safeDiv(safeMul(tokenAmount, ADDITIONAL_BONUS_NUM), ADDITIONAL_BONUS_DENOM); 370 | tokenBonusAmount = safeAdd(tokenBonusAmount, additionalBonus); 371 | additionalBonusApplied = true; 372 | } 373 | 374 | uint256 tokenTotalAmount = safeAdd(tokenAmount, tokenBonusAmount); 375 | token.issue(msg.sender, tokenTotalAmount); 376 | totalBNBContributed = safeAdd(totalBNBContributed, amountBNB); 377 | 378 | LogBNBContribution(msg.sender, amountBNB, tokenAmount, tokenBonusAmount, additionalBonusApplied, now); 379 | } 380 | 381 | /** 382 | * @dev Process ether contribution. Calc bonuses and issue tokens to contributor. 383 | */ 384 | function processContribution(address contributor, uint256 amount) private checkTime checkContribution checkCap { 385 | bool additionalBonusApplied = false; 386 | uint256 bonusNum = 0; 387 | uint256 bonusDenom = 100; 388 | (bonusNum, bonusDenom) = getBonus(); 389 | uint256 tokenBonusAmount = 0; 390 | 391 | uint256 tokenAmount = safeDiv(safeMul(amount, tokenPriceNum), tokenPriceDenom); 392 | rawTokenSupply = safeAdd(rawTokenSupply, tokenAmount); 393 | 394 | if(bonusNum > 0) { 395 | tokenBonusAmount = safeDiv(safeMul(tokenAmount, bonusNum), bonusDenom); 396 | } 397 | 398 | if(additionalBonusOwnerState[contributor] == AdditionalBonusState.Active) { 399 | additionalBonusOwnerState[contributor] = AdditionalBonusState.Applied; 400 | uint256 additionalBonus = safeDiv(safeMul(tokenAmount, ADDITIONAL_BONUS_NUM), ADDITIONAL_BONUS_DENOM); 401 | tokenBonusAmount = safeAdd(tokenBonusAmount, additionalBonus); 402 | additionalBonusApplied = true; 403 | } 404 | 405 | processPayment(contributor, amount, tokenAmount, tokenBonusAmount, additionalBonusApplied); 406 | } 407 | 408 | /** 409 | * @dev Process ether contribution before KYC. Calc bonuses and tokens to issue after KYC. 410 | */ 411 | function processReservationFundContribution( 412 | address contributor, 413 | uint256 tokenAmount, 414 | uint256 tokenBonusAmount 415 | ) external payable checkCap { 416 | require(msg.sender == address(reservationFund)); 417 | require(msg.value > 0); 418 | rawTokenSupply = safeAdd(rawTokenSupply, tokenAmount); 419 | processPayment(contributor, msg.value, tokenAmount, tokenBonusAmount, false); 420 | } 421 | 422 | function processPayment(address contributor, uint256 etherAmount, uint256 tokenAmount, uint256 tokenBonusAmount, bool additionalBonusApplied) internal { 423 | uint256 tokenTotalAmount = safeAdd(tokenAmount, tokenBonusAmount); 424 | 425 | token.issue(contributor, tokenTotalAmount); 426 | fund.processContribution.value(etherAmount)(contributor); 427 | totalEtherContributed = safeAdd(totalEtherContributed, etherAmount); 428 | userTotalContributed[contributor] = safeAdd(userTotalContributed[contributor], etherAmount); 429 | LogContribution(contributor, etherAmount, tokenAmount, tokenBonusAmount, additionalBonusApplied, now); 430 | } 431 | 432 | /** 433 | * @dev Force crowdsale refund 434 | */ 435 | function forceCrowdsaleRefund() public onlyOwner { 436 | pause(); 437 | fund.enableCrowdsaleRefund(); 438 | reservationFund.onCrowdsaleEnd(); 439 | token.finishIssuance(); 440 | bnbRefundEnabled = true; 441 | } 442 | 443 | /** 444 | * @dev Finalize crowdsale if we reached hard cap or current time > SALE_END_TIME 445 | */ 446 | function finalizeCrowdsale() public onlyOwner { 447 | if( 448 | totalEtherContributed >= safeSub(hardCap, 1000 ether) || 449 | (now >= SALE_END_TIME && totalEtherContributed >= softCap) 450 | ) { 451 | fund.onCrowdsaleEnd(); 452 | reservationFund.onCrowdsaleEnd(); 453 | bnbWithdrawEnabled = true; 454 | 455 | // Referral 456 | uint256 referralTokenAmount = safeDiv(rawTokenSupply, 10); 457 | token.issue(referralTokenWallet, referralTokenAmount); 458 | 459 | // Foundation 460 | uint256 foundationTokenAmount = safeDiv(token.totalSupply(), 2); // 20% 461 | token.issue(address(lockedTokens), foundationTokenAmount); 462 | lockedTokens.addTokens(foundationTokenWallet, foundationTokenAmount, now + 365 days); 463 | uint256 suppliedTokenAmount = token.totalSupply(); 464 | 465 | // Reserve 466 | uint256 reservedTokenAmount = safeDiv(safeMul(suppliedTokenAmount, 3), 10); // 18% 467 | token.issue(address(lockedTokens), reservedTokenAmount); 468 | lockedTokens.addTokens(reserveTokenWallet, reservedTokenAmount, now + 183 days); 469 | 470 | // Advisors 471 | uint256 advisorsTokenAmount = safeDiv(suppliedTokenAmount, 10); // 6% 472 | token.issue(advisorsTokenWallet, advisorsTokenAmount); 473 | 474 | // Company 475 | uint256 companyTokenAmount = safeDiv(suppliedTokenAmount, 4); // 15% 476 | token.issue(address(lockedTokens), companyTokenAmount); 477 | lockedTokens.addTokens(companyTokenWallet, companyTokenAmount, now + 730 days); 478 | 479 | // Bounty 480 | uint256 bountyTokenAmount = safeDiv(suppliedTokenAmount, 60); // 1% 481 | token.issue(bountyTokenWallet, bountyTokenAmount); 482 | token.finishIssuance(); 483 | } else if(now >= SALE_END_TIME) { 484 | // Enable fund`s crowdsale refund if soft cap is not reached 485 | fund.enableCrowdsaleRefund(); 486 | reservationFund.onCrowdsaleEnd(); 487 | token.finishIssuance(); 488 | bnbRefundEnabled = true; 489 | } 490 | } 491 | 492 | /** 493 | * @dev Withdraw bnb after crowdsale if crowdsale is not in refund mode 494 | */ 495 | function withdrawBNB() public onlyOwner { 496 | require(bnbWithdrawEnabled); 497 | // BNB transfer 498 | if(bnbToken.balanceOf(address(this)) > 0) { 499 | bnbToken.transfer(bnbTokenWallet, bnbToken.balanceOf(address(this))); 500 | } 501 | } 502 | 503 | /** 504 | * @dev Function is called by contributor to refund BNB token payments if crowdsale failed to reach soft cap 505 | */ 506 | function refundBNBContributor() public { 507 | require(bnbRefundEnabled); 508 | require(bnbContributions[msg.sender] > 0); 509 | uint256 amount = bnbContributions[msg.sender]; 510 | bnbContributions[msg.sender] = 0; 511 | bnbToken.transfer(msg.sender, amount); 512 | token.destroy(msg.sender, token.balanceOf(msg.sender)); 513 | } 514 | } 515 | --------------------------------------------------------------------------------