/** * Source Code first verified at https://etherscan.io on Monday, April 29, 2019 (UTC) */ // File: contracts/ownership/Ownable.sol pragma solidity 0.4.25; /// @title Ownable /// @dev Provide a simple access control with a single authority: the owner contract Ownable { // Ethereum address of current owner address public owner; // Ethereum address of the next owner // (has to claim ownership first to become effective owner) address public newOwner; // @dev Log event on ownership transferred // @param previousOwner Ethereum address of previous owner // @param newOwner Ethereum address of new owner event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /// @dev Forbid call by anyone but owner modifier onlyOwner() { require(msg.sender == owner, "Restricted to owner"); _; } /// @dev Deployer account becomes initial owner constructor() public { owner = msg.sender; } /// @dev Transfer ownership to a new Ethereum account (safe method) /// Note: the new owner has to claim his ownership to become effective owner. /// @param _newOwner Ethereum address to transfer ownership to function transferOwnership(address _newOwner) public onlyOwner { require(_newOwner != address(0x0), "New owner is zero"); newOwner = _newOwner; } /// @dev Transfer ownership to a new Ethereum account (unsafe method) /// Note: It's strongly recommended to use the safe variant via transferOwnership /// and claimOwnership, to prevent accidental transfers to a wrong address. /// @param _newOwner Ethereum address to transfer ownership to function transferOwnershipUnsafe(address _newOwner) public onlyOwner { require(_newOwner != address(0x0), "New owner is zero"); _transferOwnership(_newOwner); } /// @dev Become effective owner (if dedicated so by previous owner) function claimOwnership() public { require(msg.sender == newOwner, "Restricted to new owner"); _transferOwnership(msg.sender); } /// @dev Transfer ownership (internal method) /// @param _newOwner Ethereum address to transfer ownership to function _transferOwnership(address _newOwner) private { if (_newOwner != owner) { emit OwnershipTransferred(owner, _newOwner); owner = _newOwner; } } } // File: contracts/whitelist/Whitelist.sol pragma solidity 0.4.25; /// @title Whitelist /// @author STOKR contract Whitelist is Ownable { // Set of admins mapping(address => bool) public admins; // Set of Whitelisted addresses mapping(address => bool) public isWhitelisted; /// @dev Log entry on admin added to set /// @param admin An Ethereum address event AdminAdded(address indexed admin); /// @dev Log entry on admin removed from set /// @param admin An Ethereum address event AdminRemoved(address indexed admin); /// @dev Log entry on investor added set /// @param admin An Ethereum address /// @param investor An Ethereum address event InvestorAdded(address indexed admin, address indexed investor); /// @dev Log entry on investor removed from set /// @param admin An Ethereum address /// @param investor An Ethereum address event InvestorRemoved(address indexed admin, address indexed investor); /// @dev Only admin modifier onlyAdmin() { require(admins[msg.sender], "Restricted to whitelist admin"); _; } /// @dev Add admin to set /// @param _admin An Ethereum address function addAdmin(address _admin) public onlyOwner { require(_admin != address(0x0), "Whitelist admin is zero"); if (!admins[_admin]) { admins[_admin] = true; emit AdminAdded(_admin); } } /// @dev Remove admin from set /// @param _admin An Ethereum address function removeAdmin(address _admin) public onlyOwner { require(_admin != address(0x0), "Whitelist admin is zero"); // Necessary? if (admins[_admin]) { admins[_admin] = false; emit AdminRemoved(_admin); } } /// @dev Add investor to set of whitelisted addresses /// @param _investors A list where each entry is an Ethereum address function addToWhitelist(address[] _investors) external onlyAdmin { for (uint256 i = 0; i < _investors.length; i++) { if (!isWhitelisted[_investors[i]]) { isWhitelisted[_investors[i]] = true; emit InvestorAdded(msg.sender, _investors[i]); } } } /// @dev Remove investor from set of whitelisted addresses /// @param _investors A list where each entry is an Ethereum address function removeFromWhitelist(address[] _investors) external onlyAdmin { for (uint256 i = 0; i < _investors.length; i++) { if (isWhitelisted[_investors[i]]) { isWhitelisted[_investors[i]] = false; emit InvestorRemoved(msg.sender, _investors[i]); } } } } // File: contracts/whitelist/Whitelisted.sol pragma solidity 0.4.25; /// @title Whitelisted /// @author STOKR contract Whitelisted is Ownable { Whitelist public whitelist; /// @dev Log entry on change of whitelist contract instance /// @param previous Ethereum address of previous whitelist /// @param current Ethereum address of new whitelist event WhitelistChange(address indexed previous, address indexed current); /// @dev Ensure only whitelisted addresses can call modifier onlyWhitelisted(address _address) { require(whitelist.isWhitelisted(_address), "Address is not whitelisted"); _; } /// @dev Constructor /// @param _whitelist address of whitelist contract constructor(Whitelist _whitelist) public { setWhitelist(_whitelist); } /// @dev Set the address of whitelist /// @param _newWhitelist An Ethereum address function setWhitelist(Whitelist _newWhitelist) public onlyOwner { require(address(_newWhitelist) != address(0x0), "Whitelist address is zero"); if (address(_newWhitelist) != address(whitelist)) { emit WhitelistChange(address(whitelist), address(_newWhitelist)); whitelist = Whitelist(_newWhitelist); } } } // File: contracts/token/TokenRecoverable.sol pragma solidity 0.4.25; /// @title TokenRecoverable /// @author STOKR contract TokenRecoverable is Ownable { // Address that can do the TokenRecovery address public tokenRecoverer; /// @dev Event emitted when the TokenRecoverer changes /// @param previous Ethereum address of previous token recoverer /// @param current Ethereum address of new token recoverer event TokenRecovererChange(address indexed previous, address indexed current); /// @dev Event emitted in case of a TokenRecovery /// @param oldAddress Ethereum address of old account /// @param newAddress Ethereum address of new account event TokenRecovery(address indexed oldAddress, address indexed newAddress); /// @dev Restrict operation to token recoverer modifier onlyTokenRecoverer() { require(msg.sender == tokenRecoverer, "Restricted to token recoverer"); _; } /// @dev Constructor /// @param _tokenRecoverer Ethereum address of token recoverer constructor(address _tokenRecoverer) public { setTokenRecoverer(_tokenRecoverer); } /// @dev Set token recoverer /// @param _newTokenRecoverer Ethereum address of new token recoverer function setTokenRecoverer(address _newTokenRecoverer) public onlyOwner { require(_newTokenRecoverer != address(0x0), "New token recoverer is zero"); if (_newTokenRecoverer != tokenRecoverer) { emit TokenRecovererChange(tokenRecoverer, _newTokenRecoverer); tokenRecoverer = _newTokenRecoverer; } } /// @dev Recover token /// @param _oldAddress address /// @param _newAddress address function recoverToken(address _oldAddress, address _newAddress) public; } // File: contracts/token/ERC20.sol pragma solidity 0.4.25; /// @title ERC20 interface /// @dev see https://github.com/ethereum/EIPs/issues/20 interface ERC20 { event Transfer(address indexed from, address indexed to, uint value); event Approval(address indexed owner, address indexed spender, uint value); function totalSupply() external view returns (uint); function balanceOf(address _owner) external view returns (uint); function allowance(address _owner, address _spender) external view returns (uint); function approve(address _spender, uint _value) external returns (bool); function transfer(address _to, uint _value) external returns (bool); function transferFrom(address _from, address _to, uint _value) external returns (bool); } // File: contracts/math/SafeMath.sol pragma solidity 0.4.25; /// @title SafeMath /// @dev Math operations with safety checks that throw on error library SafeMath { /// @dev Add two integers function add(uint a, uint b) internal pure returns (uint) { uint c = a + b; assert(c >= a); return c; } /// @dev Subtract two integers function sub(uint a, uint b) internal pure returns (uint) { assert(b <= a); return a - b; } /// @dev Multiply tow integers function mul(uint a, uint b) internal pure returns (uint) { if (a == 0) { return 0; } uint c = a * b; assert(c / a == b); return c; } /// @dev Floor divide two integers function div(uint a, uint b) internal pure returns (uint) { return a / b; } } // File: contracts/token/ProfitSharing.sol pragma solidity 0.4.25; /// @title ProfitSharing /// @author STOKR contract ProfitSharing is Ownable { using SafeMath for uint; // An InvestorAccount object keeps track of the investor's // - balance: amount of tokens he/she holds (always up-to-date) // - profitShare: amount of wei this token owed him/her at the last update // - lastTotalProfits: determines when his/her profitShare was updated // Note, this construction requires: // - totalProfits to never decrease // - totalSupply to be fixed // - profitShare of all involved parties to get updated prior to any token transfer // - lastTotalProfits to be set to current totalProfits upon profitShare update struct InvestorAccount { uint balance; // token balance uint lastTotalProfits; // totalProfits [wei] at the time of last profit share update uint profitShare; // profit share [wei] of last update } // Investor account database mapping(address => InvestorAccount) public accounts; // Authority who is allowed to deposit profits [wei] on this address public profitDepositor; // Authority who is allowed to distribute profit shares [wei] to investors // (so, that they don't need to withdraw it by themselves) address public profitDistributor; // Amount of total profits [wei] stored to this token // In contrast to the wei balance (which may be reduced due to profit share withdrawal) // this value will never decrease uint public totalProfits; // As long as the total supply isn't fixed, i.e. new tokens can appear out of thin air, // the investors' profit shares aren't determined bool public totalSupplyIsFixed; // Total amount of tokens uint internal totalSupply_; /// @dev Log entry on change of profit deposit authority /// @param previous Ethereum address of previous profit depositor /// @param current Ethereum address of new profit depositor event ProfitDepositorChange(address indexed previous, address indexed current); /// @dev Log entry on change of profit distribution authority /// @param previous Ethereum address of previous profit distributor /// @param current Ethereum address of new profit distributor event ProfitDistributorChange(address indexed previous, address indexed current); /// @dev Log entry on profit deposit /// @param depositor Profit depositor's address /// @param amount Deposited profits in wei event ProfitDeposit(address indexed depositor, uint amount); /// @dev Log entry on profit share update /// @param investor Investor's address /// @param amount New wei amount the token owes the investor event ProfitShareUpdate(address indexed investor, uint amount); /// @dev Log entry on profit withdrawal /// @param investor Investor's address /// @param amount Wei amount the investor withdrew from this token event ProfitShareWithdrawal(address indexed investor, address indexed beneficiary, uint amount); /// @dev Restrict operation to profit deposit authority only modifier onlyProfitDepositor() { require(msg.sender == profitDepositor, "Restricted to profit depositor"); _; } /// @dev Restrict operation to profit distribution authority only modifier onlyProfitDistributor() { require(msg.sender == profitDistributor, "Restricted to profit distributor"); _; } /// @dev Restrict operation to when total supply doesn't change anymore modifier onlyWhenTotalSupplyIsFixed() { require(totalSupplyIsFixed, "Total supply may change"); _; } /// @dev Constructor /// @param _profitDepositor Profit deposit authority constructor(address _profitDepositor, address _profitDistributor) public { setProfitDepositor(_profitDepositor); setProfitDistributor(_profitDistributor); } /// @dev Profit deposit if possible via fallback function function () public payable { require(msg.data.length == 0, "Fallback call with data"); depositProfit(); } /// @dev Change profit depositor /// @param _newProfitDepositor An Ethereum address function setProfitDepositor(address _newProfitDepositor) public onlyOwner { require(_newProfitDepositor != address(0x0), "New profit depositor is zero"); if (_newProfitDepositor != profitDepositor) { emit ProfitDepositorChange(profitDepositor, _newProfitDepositor); profitDepositor = _newProfitDepositor; } } /// @dev Change profit distributor /// @param _newProfitDistributor An Ethereum address function setProfitDistributor(address _newProfitDistributor) public onlyOwner { require(_newProfitDistributor != address(0x0), "New profit distributor is zero"); if (_newProfitDistributor != profitDistributor) { emit ProfitDistributorChange(profitDistributor, _newProfitDistributor); profitDistributor = _newProfitDistributor; } } /// @dev Deposit profit function depositProfit() public payable onlyProfitDepositor onlyWhenTotalSupplyIsFixed { require(totalSupply_ > 0, "Total supply is zero"); totalProfits = totalProfits.add(msg.value); emit ProfitDeposit(msg.sender, msg.value); } /// @dev Profit share owing /// @param _investor An Ethereum address /// @return A positive number function profitShareOwing(address _investor) public view returns (uint) { if (!totalSupplyIsFixed || totalSupply_ == 0) { return 0; } InvestorAccount memory account = accounts[_investor]; return totalProfits.sub(account.lastTotalProfits) .mul(account.balance) .div(totalSupply_) .add(account.profitShare); } /// @dev Update profit share /// @param _investor An Ethereum address function updateProfitShare(address _investor) public onlyWhenTotalSupplyIsFixed { uint newProfitShare = profitShareOwing(_investor); accounts[_investor].lastTotalProfits = totalProfits; accounts[_investor].profitShare = newProfitShare; emit ProfitShareUpdate(_investor, newProfitShare); } /// @dev Withdraw profit share function withdrawProfitShare() public { _withdrawProfitShare(msg.sender, msg.sender); } function withdrawProfitShareTo(address _beneficiary) public { _withdrawProfitShare(msg.sender, _beneficiary); } /// @dev Withdraw profit share function withdrawProfitShares(address[] _investors) external onlyProfitDistributor { for (uint i = 0; i < _investors.length; ++i) { _withdrawProfitShare(_investors[i], _investors[i]); } } /// @dev Withdraw profit share function _withdrawProfitShare(address _investor, address _beneficiary) internal { updateProfitShare(_investor); uint withdrawnProfitShare = accounts[_investor].profitShare; accounts[_investor].profitShare = 0; _beneficiary.transfer(withdrawnProfitShare); emit ProfitShareWithdrawal(_investor, _beneficiary, withdrawnProfitShare); } } // File: contracts/token/MintableToken.sol pragma solidity 0.4.25; /// @title MintableToken /// @author STOKR /// @dev Extension of the ERC20 compliant ProfitSharing Token /// that allows the creation of tokens via minting for a /// limited time period (until minting gets finished). contract MintableToken is ERC20, ProfitSharing, Whitelisted { address public minter; uint public numberOfInvestors = 0; /// @dev Log entry on mint /// @param to Beneficiary who received the newly minted tokens /// @param amount The amount of minted token units event Minted(address indexed to, uint amount); /// @dev Log entry on mint finished event MintFinished(); /// @dev Restrict an operation to be callable only by the minter modifier onlyMinter() { require(msg.sender == minter, "Restricted to minter"); _; } /// @dev Restrict an operation to be executable only while minting was not finished modifier canMint() { require(!totalSupplyIsFixed, "Total supply has been fixed"); _; } /// @dev Set minter authority /// @param _minter Ethereum address of minter authority function setMinter(address _minter) public onlyOwner { require(minter == address(0x0), "Minter has already been set"); require(_minter != address(0x0), "Minter is zero"); minter = _minter; } /// @dev Mint tokens, i.e. create tokens out of thin air /// @param _to Beneficiary who will receive the newly minted tokens /// @param _amount The amount of minted token units function mint(address _to, uint _amount) public onlyMinter canMint onlyWhitelisted(_to) { if (accounts[_to].balance == 0) { numberOfInvestors++; } totalSupply_ = totalSupply_.add(_amount); accounts[_to].balance = accounts[_to].balance.add(_amount); emit Minted(_to, _amount); emit Transfer(address(0x0), _to, _amount); } /// @dev Finish minting -- this should be irreversible function finishMinting() public onlyMinter canMint { totalSupplyIsFixed = true; emit MintFinished(); } } // File: contracts/token/StokrToken.sol pragma solidity 0.4.25; /// @title StokrToken /// @author Stokr contract StokrToken is MintableToken, TokenRecoverable { string public name; string public symbol; uint8 public constant decimals = 18; mapping(address => mapping(address => uint)) internal allowance_; /// @dev Log entry on self destruction of the token event TokenDestroyed(); /// @dev Constructor /// @param _whitelist Ethereum address of whitelist contract /// @param _tokenRecoverer Ethereum address of token recoverer constructor( string _name, string _symbol, Whitelist _whitelist, address _profitDepositor, address _profitDistributor, address _tokenRecoverer ) public Whitelisted(_whitelist) ProfitSharing(_profitDepositor, _profitDistributor) TokenRecoverable(_tokenRecoverer) { name = _name; symbol = _symbol; } /// @dev Self destruct can only be called by crowdsale contract in case the goal wasn't reached function destruct() public onlyMinter { emit TokenDestroyed(); selfdestruct(owner); } /// @dev Recover token /// @param _oldAddress address of old account /// @param _newAddress address of new account function recoverToken(address _oldAddress, address _newAddress) public onlyTokenRecoverer onlyWhitelisted(_newAddress) { // Ensure that new address is *not* an existing account. // Check for account.profitShare is not needed because of following implication: // (account.lastTotalProfits == 0) ==> (account.profitShare == 0) require(accounts[_newAddress].balance == 0 && accounts[_newAddress].lastTotalProfits == 0, "New address exists already"); updateProfitShare(_oldAddress); accounts[_newAddress] = accounts[_oldAddress]; delete accounts[_oldAddress]; emit TokenRecovery(_oldAddress, _newAddress); emit Transfer(_oldAddress, _newAddress, accounts[_newAddress].balance); } /// @dev Total supply of this token /// @return Token amount function totalSupply() public view returns (uint) { return totalSupply_; } /// @dev Token balance /// @param _investor Ethereum address of token holder /// @return Token amount function balanceOf(address _investor) public view returns (uint) { return accounts[_investor].balance; } /// @dev Allowed token amount a third party trustee may transfer /// @param _investor Ethereum address of token holder /// @param _spender Ethereum address of third party /// @return Allowed token amount function allowance(address _investor, address _spender) public view returns (uint) { return allowance_[_investor][_spender]; } /// @dev Approve a third party trustee to transfer tokens /// Note: additional requirements are enforced within internal function. /// @param _spender Ethereum address of third party /// @param _value Maximum token amount that is allowed to get transferred /// @return Always true function approve(address _spender, uint _value) public returns (bool) { return _approve(msg.sender, _spender, _value); } /// @dev Increase the amount of tokens a third party trustee may transfer /// Note: additional requirements are enforces within internal function. /// @param _spender Ethereum address of third party /// @param _amount Additional token amount that is allowed to get transferred /// @return Always true function increaseAllowance(address _spender, uint _amount) public returns (bool) { require(allowance_[msg.sender][_spender] + _amount >= _amount, "Allowance overflow"); return _approve(msg.sender, _spender, allowance_[msg.sender][_spender].add(_amount)); } /// @dev Decrease the amount of tokens a third party trustee may transfer /// Note: additional requirements are enforces within internal function. /// @param _spender Ethereum address of third party /// @param _amount Reduced token amount that is allowed to get transferred /// @return Always true function decreaseAllowance(address _spender, uint _amount) public returns (bool) { require(_amount <= allowance_[msg.sender][_spender], "Amount exceeds allowance"); return _approve(msg.sender, _spender, allowance_[msg.sender][_spender].sub(_amount)); } /// @dev Check if a token transfer is possible /// @param _from Ethereum address of token sender /// @param _to Ethereum address of token recipient /// @param _value Token amount to transfer /// @return True iff a transfer with given pramaters would succeed function canTransfer(address _from, address _to, uint _value) public view returns (bool) { return totalSupplyIsFixed && _from != address(0x0) && _to != address(0x0) && _value <= accounts[_from].balance && whitelist.isWhitelisted(_from) && whitelist.isWhitelisted(_to); } /// @dev Check if a token transfer by third party is possible /// @param _spender Ethereum address of third party trustee /// @param _from Ethereum address of token holder /// @param _to Ethereum address of token recipient /// @param _value Token amount to transfer /// @return True iff a transfer with given pramaters would succeed function canTransferFrom(address _spender, address _from, address _to, uint _value) public view returns (bool) { return canTransfer(_from, _to, _value) && _value <= allowance_[_from][_spender]; } /// @dev Token transfer /// Note: additional requirements are enforces within internal function. /// @param _to Ethereum address of token recipient /// @param _value Token amount to transfer /// @return Always true function transfer(address _to, uint _value) public returns (bool) { return _transfer(msg.sender, _to, _value); } /// @dev Token transfer by a third party /// Note: additional requirements are enforces within internal function. /// @param _from Ethereum address of token holder /// @param _to Ethereum address of token recipient /// @param _value Token amount to transfer /// @return Always true function transferFrom(address _from, address _to, uint _value) public returns (bool) { require(_value <= allowance_[_from][msg.sender], "Amount exceeds allowance"); return _approve(_from, msg.sender, allowance_[_from][msg.sender].sub(_value)) && _transfer(_from, _to, _value); } /// @dev Approve a third party trustee to transfer tokens (internal implementation) /// @param _from Ethereum address of token holder /// @param _spender Ethereum address of third party /// @param _value Maximum token amount the trustee is allowed to transfer /// @return Always true function _approve(address _from, address _spender, uint _value) internal onlyWhitelisted(_from) onlyWhenTotalSupplyIsFixed returns (bool) { allowance_[_from][_spender] = _value; emit Approval(_from, _spender, _value); return true; } /// @dev Token transfer (internal implementation) /// @param _from Ethereum address of token sender /// @param _to Ethereum address of token recipient /// @param _value Token amount to transfer /// @return Always true function _transfer(address _from, address _to, uint _value) internal onlyWhitelisted(_from) onlyWhitelisted(_to) onlyWhenTotalSupplyIsFixed returns (bool) { require(_to != address(0x0), "Recipient is zero"); require(_value <= accounts[_from].balance, "Amount exceeds balance"); updateProfitShare(_from); updateProfitShare(_to); accounts[_from].balance = accounts[_from].balance.sub(_value); accounts[_to].balance = accounts[_to].balance.add(_value); emit Transfer(_from, _to, _value); return true; } } // File: contracts/token/StokrTokenFactory.sol pragma solidity 0.4.25; // Helper contract to deploy a new StokrToken contract contract StokrTokenFactory { function createNewToken( string name, string symbol, Whitelist whitelist, address profitDepositor, address profitDistributor, address tokenRecoverer ) public returns (StokrToken) { StokrToken token = new StokrToken( name, symbol, whitelist, profitDepositor, profitDistributor, tokenRecoverer); token.transferOwnershipUnsafe(msg.sender); return token; } } // File: contracts/crowdsale/RateSourceInterface.sol pragma solidity 0.4.25; /// @title RateSource /// @author STOKR interface RateSource { /// @dev The current price of an Ether in EUR cents /// @return Current ether rate function etherRate() external returns(uint); } // File: contracts/crowdsale/MintingCrowdsale.sol pragma solidity 0.4.25; /// @title MintingCrowdsale /// @author STOKR contract MintingCrowdsale is Ownable { using SafeMath for uint; // Maximum Time of offering period after extension uint constant MAXOFFERINGPERIOD = 80 days; // Ether rate oracle contract providing the price of an Ether in EUR cents RateSource public rateSource; // The token to be sold // In the following, the term "token unit" always refers to the smallest // and non-divisible quantum. Thus, token unit amounts are always integers. // One token is expected to consist of 10^18 token units. MintableToken public token; // Token amounts in token units // The public and the private sale are both capped (i.e. two distinct token pools) // The tokenRemaining variables keep track of how many token units are available // for the respective type of sale uint public tokenCapOfPublicSale; uint public tokenCapOfPrivateSale; uint public tokenRemainingForPublicSale; uint public tokenRemainingForPrivateSale; // Prices are in Euro cents (i.e. 1/100 EUR) uint public tokenPrice; // The minimum amount of tokens a purchaser has to buy via one transaction uint public tokenPurchaseMinimum; // The maximum total amount of tokens a purchaser may buy during start phase uint public tokenPurchaseLimit; // Total token purchased by investor (while purchase amount is limited) mapping(address => uint) public tokenPurchased; // Public sale period uint public openingTime; uint public closingTime; uint public limitEndTime; // Ethereum address where invested funds will be transferred to address public companyWallet; // Amount and receiver of reserved tokens uint public tokenReservePerMill; address public reserveAccount; // Wether this crowdsale was finalized or not bool public isFinalized = false; /// @dev Log entry upon token distribution event /// @param beneficiary Ethereum address of token recipient /// @param amount Number of token units /// @param isPublicSale Whether the distribution was via public sale event TokenDistribution(address indexed beneficiary, uint amount, bool isPublicSale); /// @dev Log entry upon token purchase event /// @param buyer Ethereum address of token purchaser /// @param value Worth in wei of purchased token amount /// @param amount Number of token units event TokenPurchase(address indexed buyer, uint value, uint amount); /// @dev Log entry upon rate change event /// @param previous Previous closing time of sale /// @param current Current closing time of sale event ClosingTimeChange(uint previous, uint current); /// @dev Log entry upon finalization event event Finalization(); /// @dev Constructor /// @param _rateSource Ether rate oracle contract /// @param _token The token to be sold /// @param _tokenCapOfPublicSale Maximum number of token units to mint in public sale /// @param _tokenCapOfPrivateSale Maximum number of token units to mint in private sale /// @param _tokenPurchaseMinimum Minimum amount of tokens an investor has to buy at once /// @param _tokenPurchaseLimit Maximum total token amounts individually buyable in limit phase /// @param _tokenPrice Price of a token in EUR cent /// @param _openingTime Block (Unix) timestamp of sale opening time /// @param _closingTime Block (Unix) timestamp of sale closing time /// @param _limitEndTime Block (Unix) timestamp until token purchases are limited /// @param _companyWallet Ethereum account who will receive sent ether /// @param _tokenReservePerMill Per mill amount of sold tokens to mint for reserve account /// @param _reserveAccount Ethereum address of reserve tokens recipient constructor( RateSource _rateSource, MintableToken _token, uint _tokenCapOfPublicSale, uint _tokenCapOfPrivateSale, uint _tokenPurchaseMinimum, uint _tokenPurchaseLimit, uint _tokenReservePerMill, uint _tokenPrice, uint _openingTime, uint _closingTime, uint _limitEndTime, address _companyWallet, address _reserveAccount ) public { require(address(_rateSource) != address(0x0), "Rate source is zero"); require(address(_token) != address(0x0), "Token address is zero"); require(_token.minter() == address(0x0), "Token has another minter"); require(_tokenCapOfPublicSale > 0, "Cap of public sale is zero"); require(_tokenCapOfPrivateSale > 0, "Cap of private sale is zero"); require(_tokenPurchaseMinimum <= _tokenCapOfPublicSale && _tokenPurchaseMinimum <= _tokenCapOfPrivateSale, "Purchase minimum exceeds cap"); require(_tokenPrice > 0, "Token price is zero"); require(_openingTime >= now, "Opening lies in the past"); require(_closingTime >= _openingTime, "Closing lies before opening"); require(_companyWallet != address(0x0), "Company wallet is zero"); require(_reserveAccount != address(0x0), "Reserve account is zero"); // Note: There are no time related requirements regarding limitEndTime. // If it's below openingTime, token purchases will never be limited. // If it's above closingTime, token purchases will always be limited. if (_limitEndTime > _openingTime) { // But, if there's a purchase limitation phase, the limit must be at // least the purchase minimum or above to make purchases possible. require(_tokenPurchaseLimit >= _tokenPurchaseMinimum, "Purchase limit is below minimum"); } // Utilize safe math to ensure the sum of three token pools does't overflow _tokenCapOfPublicSale.add(_tokenCapOfPrivateSale).mul(_tokenReservePerMill); rateSource = _rateSource; token = _token; tokenCapOfPublicSale = _tokenCapOfPublicSale; tokenCapOfPrivateSale = _tokenCapOfPrivateSale; tokenPurchaseMinimum = _tokenPurchaseMinimum; tokenPurchaseLimit= _tokenPurchaseLimit; tokenReservePerMill = _tokenReservePerMill; tokenPrice = _tokenPrice; openingTime = _openingTime; closingTime = _closingTime; limitEndTime = _limitEndTime; companyWallet = _companyWallet; reserveAccount = _reserveAccount; tokenRemainingForPublicSale = _tokenCapOfPublicSale; tokenRemainingForPrivateSale = _tokenCapOfPrivateSale; } /// @dev Fallback function: buys tokens function () public payable { require(msg.data.length == 0, "Fallback call with data"); buyTokens(); } /// @dev Distribute tokens purchased off-chain via public sale /// Note: additional requirements are enforced in internal function. /// @param beneficiaries List of recipients' Ethereum addresses /// @param amounts List of token units each recipient will receive function distributeTokensViaPublicSale(address[] beneficiaries, uint[] amounts) external { tokenRemainingForPublicSale = distributeTokens(tokenRemainingForPublicSale, beneficiaries, amounts, true); } /// @dev Distribute tokens purchased off-chain via private sale /// Note: additional requirements are enforced in internal function. /// @param beneficiaries List of recipients' Ethereum addresses /// @param amounts List of token units each recipient will receive function distributeTokensViaPrivateSale(address[] beneficiaries, uint[] amounts) external { tokenRemainingForPrivateSale = distributeTokens(tokenRemainingForPrivateSale, beneficiaries, amounts, false); } /// @dev Check whether the sale has closed /// @return True iff sale closing time has passed function hasClosed() public view returns (bool) { return now >= closingTime; } /// @dev Check wether the sale is open /// @return True iff sale opening time has passed and sale is not closed yet function isOpen() public view returns (bool) { return now >= openingTime && !hasClosed(); } /// @dev Determine the remaining open time of sale /// @return Time in seconds until sale gets closed, or 0 if sale was closed function timeRemaining() public view returns (uint) { if (hasClosed()) { return 0; } return closingTime - now; } /// @dev Determine the amount of sold tokens (off-chain and on-chain) /// @return Token units amount function tokenSold() public view returns (uint) { return (tokenCapOfPublicSale - tokenRemainingForPublicSale) + (tokenCapOfPrivateSale - tokenRemainingForPrivateSale); } /// @dev Purchase tokens function buyTokens() public payable { require(isOpen(), "Sale is not open"); uint etherRate = rateSource.etherRate(); require(etherRate > 0, "Ether rate is zero"); // Units: [1e-18*ether] * [cent/ether] / [cent/token] => [1e-18*token] uint amount = msg.value.mul(etherRate).div(tokenPrice); require(amount <= tokenRemainingForPublicSale, "Not enough tokens available"); require(amount >= tokenPurchaseMinimum, "Investment is too low"); // Is the total amount an investor can purchase with Ether limited? if (now < limitEndTime) { uint purchased = tokenPurchased[msg.sender].add(amount); require(purchased <= tokenPurchaseLimit, "Purchase limit reached"); tokenPurchased[msg.sender] = purchased; } tokenRemainingForPublicSale = tokenRemainingForPublicSale.sub(amount); token.mint(msg.sender, amount); forwardFunds(); emit TokenPurchase(msg.sender, msg.value, amount); } /// @dev Extend the offering period of the crowd sale. /// @param _newClosingTime new closingTime of the crowdsale function changeClosingTime(uint _newClosingTime) public onlyOwner { require(!hasClosed(), "Sale has already ended"); require(_newClosingTime > now, "ClosingTime not in the future"); require(_newClosingTime > openingTime, "New offering is zero"); require(_newClosingTime - openingTime <= MAXOFFERINGPERIOD, "New offering too long"); emit ClosingTimeChange(closingTime, _newClosingTime); closingTime = _newClosingTime; } /// @dev Finalize, i.e. end token minting phase and enable token transfers function finalize() public onlyOwner { require(!isFinalized, "Sale has already been finalized"); require(hasClosed(), "Sale has not closed"); if (tokenReservePerMill > 0) { token.mint(reserveAccount, tokenSold().mul(tokenReservePerMill).div(1000)); } token.finishMinting(); isFinalized = true; emit Finalization(); } /// @dev Distribute tokens purchased off-chain (in Euro) to investors /// @param tokenRemaining Token units available for sale /// @param beneficiaries Ethereum addresses of purchasers /// @param amounts Token unit amounts to deliver to each investor /// @return Token units available for sale after distribution function distributeTokens(uint tokenRemaining, address[] beneficiaries, uint[] amounts, bool isPublicSale) internal onlyOwner returns (uint) { require(!isFinalized, "Sale has been finalized"); require(beneficiaries.length == amounts.length, "Lengths are different"); for (uint i = 0; i < beneficiaries.length; ++i) { address beneficiary = beneficiaries[i]; uint amount = amounts[i]; require(amount <= tokenRemaining, "Not enough tokens available"); tokenRemaining = tokenRemaining.sub(amount); token.mint(beneficiary, amount); emit TokenDistribution(beneficiary, amount, isPublicSale); } return tokenRemaining; } /// @dev Forward invested ether to company wallet function forwardFunds() internal { companyWallet.transfer(address(this).balance); } } // File: contracts/crowdsale/StokrCrowdsale.sol pragma solidity 0.4.25; /// @title StokrCrowdsale /// @author STOKR contract StokrCrowdsale is MintingCrowdsale { // Soft cap in token units uint public tokenGoal; // As long as the goal is not reached funds of purchases are held back // and investments are assigned to investors here to enable a refunding // if the goal is missed upon finalization mapping(address => uint) public investments; // Log entry upon investor refund event event InvestorRefund(address indexed investor, uint value); /// @dev Constructor /// @param _token The token /// @param _tokenCapOfPublicSale Available token units for public sale /// @param _tokenCapOfPrivateSale Available token units for private sale /// @param _tokenGoal Minimum number of sold token units to be successful /// @param _tokenPurchaseMinimum Minimum amount of tokens an investor has to buy at once /// @param _tokenPurchaseLimit Maximum total token amounts individually buyable in limit phase /// @param _tokenReservePerMill Additional reserve tokens in per mill of sold tokens /// @param _tokenPrice Price of a token in EUR cent /// @param _rateSource Ethereum address of ether rate setting authority /// @param _openingTime Block (Unix) timestamp of sale opening time /// @param _closingTime Block (Unix) timestamp of sale closing time /// @param _limitEndTime Block (Unix) timestamp until token purchases are limited /// @param _companyWallet Ethereum account who will receive sent ether /// @param _reserveAccount An address constructor( RateSource _rateSource, StokrToken _token, uint _tokenCapOfPublicSale, uint _tokenCapOfPrivateSale, uint _tokenGoal, uint _tokenPurchaseMinimum, uint _tokenPurchaseLimit, uint _tokenReservePerMill, uint _tokenPrice, uint _openingTime, uint _closingTime, uint _limitEndTime, address _companyWallet, address _reserveAccount ) public MintingCrowdsale( _rateSource, _token, _tokenCapOfPublicSale, _tokenCapOfPrivateSale, _tokenPurchaseMinimum, _tokenPurchaseLimit, _tokenReservePerMill, _tokenPrice, _openingTime, _closingTime, _limitEndTime, _companyWallet, _reserveAccount ) { require(_tokenGoal <= _tokenCapOfPublicSale + _tokenCapOfPrivateSale, "Goal is not attainable"); tokenGoal = _tokenGoal; } /// @dev Wether the goal of sold tokens was reached or not /// @return True if the sale can be considered successful function goalReached() public view returns (bool) { return tokenSold() >= tokenGoal; } /// @dev Investors can claim refunds here if crowdsale was unsuccessful function distributeRefunds(address[] _investors) external { for (uint i = 0; i < _investors.length; ++i) { refundInvestor(_investors[i]); } } /// @dev Investors can claim refunds here if crowdsale was unsuccessful function claimRefund() public { refundInvestor(msg.sender); } /// @dev Overwritten. Kill the token if goal was missed function finalize() public onlyOwner { super.finalize(); if (!goalReached()) { StokrToken(token).destruct(); } } /// @dev Overwritten. Funds are held back until goal was reached function forwardFunds() internal { if (goalReached()) { super.forwardFunds(); } else { investments[msg.sender] = investments[msg.sender].add(msg.value); } } /// @dev Refund an investor if the sale was not successful /// @param _investor Ethereum address of investor function refundInvestor(address _investor) internal { require(isFinalized, "Sale has not been finalized"); require(!goalReached(), "Goal was reached"); uint investment = investments[_investor]; if (investment > 0) { investments[_investor] = 0; _investor.transfer(investment); emit InvestorRefund(_investor, investment); } } } // File: contracts/crowdsale/StokrCrowdsaleFactory.sol pragma solidity 0.4.25; // Helper contract to deploy a new StokrCrowdsale contract contract StokrCrowdsaleFactory { function createNewCrowdsale( StokrToken token, uint tokenPrice, uint[6] amounts, // [tokenCapOfPublicSale, tokenCapOfPrivateSale, tokenGoal, // tokenPurchaseMinimum, tokenPurchaseLimit, tokenReservePerMill] uint[3] period, // [openingTime, closingTime, limitEndTime] address[2] wallets // [companyWallet, reserveAccount] ) external returns (StokrCrowdsale) { StokrCrowdsale crowdsale = new StokrCrowdsale( RateSource(msg.sender), // rateSource token, amounts[0], // tokenCapOfPublicSale amounts[1], // tokenCapOfPrivateSale amounts[2], // tokenGoal amounts[3], // tokenPurchaseMinimum amounts[4], // tokenPurchaseLimit amounts[5], // tokenReservePerMill tokenPrice, // tokenPrice period[0], // openingTime period[1], // closingTime period[2], // limitEndTime wallets[0], // companyWallet wallets[1]); // reserveAccount crowdsale.transferOwnershipUnsafe(msg.sender); return crowdsale; } } // File: contracts/StokrProjectManager.sol pragma solidity 0.4.25; contract StokrProjectManager is Ownable, RateSource { // Project structure struct StokrProject { string name; Whitelist whitelist; StokrToken token; StokrCrowdsale crowdsale; } // Rate must be below this limit: floor(2**256 / 10) uint public constant RATE_LIMIT = uint(-1) / 10; // Block number where this contract instance was deployed uint public deploymentBlockNumber; // Ethereum address of the Ether prize setting authority address public rateAdmin; // Current price of an Ether in EUR cents uint private rate; // Current whitelist and token factory and crowdsale factory instances Whitelist public currentWhitelist; StokrTokenFactory public tokenFactory; StokrCrowdsaleFactory public crowdsaleFactory; // List of projects StokrProject[] public projects; /// @dev Log entry upon rate change event /// @param previous Previous rate in EUR cent per Ether /// @param current Current rate in EUR cent per Ether event RateChange(uint previous, uint current); /// @dev Log entry upon rate admin change event /// @param previous Ethereum address of previous rate admin /// @param current Ethereum address of current rate admin event RateAdminChange(address indexed previous, address indexed current); /// @dev Log entry upon project creation event /// @param whitelist Ethereum address of Whitelist contract /// @param token Ethereum address of StokrToken contract /// @param crowdsale Ethereum address of StokrCrowdsale contract /// @param index Index of the project within projects list event ProjectCreation( uint indexed index, address whitelist, address indexed token, address indexed crowdsale ); /// @dev Restrict operation to rate admin role modifier onlyRateAdmin() { require(msg.sender == rateAdmin, "Restricted to rate admin"); _; } /// @dev Constructor /// @param etherRate Initial price of an Ether in EUR cents constructor(uint etherRate) public { require(etherRate > 0, "Ether rate is zero"); require(etherRate < RATE_LIMIT, "Ether rate reaches limit"); deploymentBlockNumber = block.number; rate = etherRate; } /// @dev Set the current whitelist contract instance /// @param newWhitelist Whitelist instance function setWhitelist(Whitelist newWhitelist) public onlyOwner { require(address(newWhitelist) != address(0x0), "Whitelist is zero"); currentWhitelist = newWhitelist; } /// @dev Set the current token factory contract instance /// @param newTokenFactory StokrTokenFactory instance function setTokenFactory(StokrTokenFactory newTokenFactory) public onlyOwner { require(address(newTokenFactory) != address(0x0), "Token factory is zero"); tokenFactory = newTokenFactory; } /// @dev Set the current crowdsale factory contract instance /// @param newCrowdsaleFactory StokrCrowdsaleFactory instance function setCrowdsaleFactory(StokrCrowdsaleFactory newCrowdsaleFactory) public onlyOwner { require(address(newCrowdsaleFactory) != address(0x0), "Crowdsale factory is zero"); crowdsaleFactory = newCrowdsaleFactory; } /// @dev Set rate admin, i.e. the ether rate setting authority /// @param newRateAdmin Ethereum address of rate admin function setRateAdmin(address newRateAdmin) public onlyOwner { require(newRateAdmin != address(0x0), "New rate admin is zero"); if (newRateAdmin != rateAdmin) { emit RateAdminChange(rateAdmin, newRateAdmin); rateAdmin = newRateAdmin; } } /// @dev Set rate, i.e. adjust to changes of EUR/ether exchange rate /// @param newRate Rate in Euro cent per ether function setRate(uint newRate) public onlyRateAdmin { // Rate changes beyond an order of magnitude are likely just typos require(rate / 10 < newRate && newRate < 10 * rate, "Rate change too big"); require(newRate < RATE_LIMIT, "New rate reaches limit"); if (newRate != rate) { emit RateChange(rate, newRate); rate = newRate; } } /// @dev Return the current price of an Ether in EUR cents /// @return Current Ether rate function etherRate() public view returns (uint) { return rate; } /// @dev Return the number of projects deployed by this instance /// @return Projects count function projectsCount() public view returns (uint) { return projects.length; } /// @dev Create a new project, /// i.e. deploy a new token and crowdsale and store their address into projects function createNewProject( string name, string symbol, uint tokenPrice, address[3] roles, // [profitDepositor, profitDistributor, tokenRecoverer] uint[6] amounts, // [tokenCapOfPublicSale, tokenCapOfPrivateSale, tokenGoal, // tokenPurchaseMinimum, tokenPurchaseLimit, tokenReservePerMill] uint[3] period, // [openingTime, closingTime, limitEndTime] address[2] wallets // [companyWallet, reserveAccount] ) external onlyOwner { require(address(currentWhitelist) != address(0x0), "Whitelist is zero"); require(address(tokenFactory) != address(0x0), "Token factory is zero"); require(address(crowdsaleFactory) != address(0x0), "Crowdsale factory is zero"); // Parameters are given as arrays to avoid the "stack too deep" complaints of the // Solidity compiler. // Furthermore the deployment of the tokens and the crowdsale contract is done via // factory contracts whose only purpose is to deploy an instance of the respective // contract. This construction avoids the problem of limited bytecode length when // deploying contracts (see EIP170). As a side effect, it also enables the change // of one of the factories by an updated version. // Utilize the token factory to deploy a new token contract instance StokrToken token = tokenFactory.createNewToken( name, symbol, currentWhitelist, roles[0], // profitDepositor roles[1], // profitDistributor roles[2]); // tokenRecoverer // Utilize the crowdsale factory to deploy a new crowdsale contract instance StokrCrowdsale crowdsale = crowdsaleFactory.createNewCrowdsale( token, tokenPrice, amounts, period, wallets); token.setMinter(crowdsale); // The crowdsale should be the minter of the token token.transferOwnershipUnsafe(msg.sender); // to tokenOwner crowdsale.transferOwnershipUnsafe(msg.sender); // to crowdsaleOwner // Store the created project into the projects array state variable projects.push(StokrProject(name, currentWhitelist, token, crowdsale)); emit ProjectCreation(projects.length - 1, currentWhitelist, token, crowdsale); } }