pragma solidity ^0.4.24; import "./ISTO.sol"; import "../../interfaces/IST20.sol"; import "../../interfaces/IOracle.sol"; import "../../RegistryUpdater.sol"; import "../../interfaces/ISecurityTokenRegistry.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; /** * @title STO module for standard capped crowdsale */ contract USDTieredSTO is ISTO, ReentrancyGuard { using SafeMath for uint256; ///////////// // Storage // ///////////// // Address where ETH & POLY funds are delivered address public wallet; // Address of issuer reserve wallet for unsold tokens address public reserveWallet; // How many token units a buyer gets per USD per tier (multiplied by 10**18) uint256[] public ratePerTier; // How many token units a buyer gets per USD per tier (multiplied by 10**18) when investing in POLY up to tokensPerTierDiscountPoly uint256[] public ratePerTierDiscountPoly; // How many tokens are available in each tier (relative to totalSupply) uint256[] public tokensPerTierTotal; // How many token units are available in each tier (relative to totalSupply) at the ratePerTierDiscountPoly rate uint256[] public tokensPerTierDiscountPoly; // How many tokens have been minted in each tier (relative to totalSupply) uint256[] public mintedPerTierTotal; // How many tokens have been minted in each tier (relative to totalSupply) at ETH rate uint256[] public mintedPerTierETH; // How many tokens have been minted in each tier (relative to totalSupply) at regular POLY rate uint256[] public mintedPerTierRegularPoly; // How many tokens have been minted in each tier (relative to totalSupply) at discounted POLY rate uint256[] public mintedPerTierDiscountPoly; // Current tier uint8 public currentTier; // Amount of USD funds raised uint256 public fundsRaisedUSD; // Amount of ETH funds raised uint256 public fundsRaisedETH; // Amount of POLY funds raised uint256 public fundsRaisedPOLY; // Number of individual investors uint256 public investorCount; // Amount in USD invested by each address mapping (address => uint256) public investorInvestedUSD; // Amount in ETH invested by each address mapping (address => uint256) public investorInvestedETH; // Amount in POLY invested by each address mapping (address => uint256) public investorInvestedPOLY; // List of accredited investors mapping (address => bool) public accredited; // Limit in USD for non-accredited investors multiplied by 10**18 uint256 public nonAccreditedLimitUSD; // Minimum investable amount in USD uint256 public minimumInvestmentUSD; // Whether or not the STO has been finalized bool public isFinalized; // Final amount of tokens sold uint256 public finalAmountSold; // Final amount of tokens returned to issuer uint256 public finalAmountReturned; //////////// // Events // //////////// event TokenPurchase(address indexed _purchaser, address indexed _beneficiary, uint256 _tokens, uint256 _usdAmount, uint256 _tierPrice, uint8 _tier); event FundsReceivedETH(address indexed _purchaser, address indexed _beneficiary, uint256 _usdAmount, uint256 _receivedValue, uint256 _spentValue, uint256 _rate); event FundsReceivedPOLY(address indexed _purchaser, address indexed _beneficiary, uint256 _usdAmount, uint256 _receivedValue, uint256 _spentValue, uint256 _rate); event ReserveTokenMint(address indexed _owner, address indexed _wallet, uint256 _tokens, uint8 _tier); event SetAddresses( address indexed _wallet, address indexed _reserveWallet ); event SetLimits( uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD ); event SetFunding( uint8[] _fundRaiseTypes ); event SetTimes( uint256 _startTime, uint256 _endTime ); event SetTiers( uint256[] _ratePerTier, uint256[] _ratePerTierDiscountPoly, uint256[] _tokensPerTierTotal, uint256[] _tokensPerTierDiscountPoly ); /////////////// // Modifiers // /////////////// modifier validETH { require(ISecurityTokenRegistry(RegistryUpdater(securityToken).securityTokenRegistry()).getOracle(bytes32("ETH"), bytes32("USD")) != address(0), "Invalid ETHUSD Oracle"); require(fundRaiseType[uint8(FundRaiseType.ETH)]); _; } modifier validPOLY { require(ISecurityTokenRegistry(RegistryUpdater(securityToken).securityTokenRegistry()).getOracle(bytes32("POLY"), bytes32("USD")) != address(0), "Invalid ETHUSD Oracle"); require(fundRaiseType[uint8(FundRaiseType.POLY)]); _; } /////////////////////// // STO Configuration // /////////////////////// constructor (address _securityToken, address _polyAddress) public IModule(_securityToken, _polyAddress) { } /** * @notice Function used to intialize the contract variables * @param _startTime Unix timestamp at which offering get started * @param _endTime Unix timestamp at which offering get ended * @param _ratePerTier Rate (in USD) per tier (* 10**18) * @param _tokensPerTierTotal Tokens available in each tier * @param _nonAccreditedLimitUSD Limit in USD (* 10**18) for non-accredited investors * @param _minimumInvestmentUSD Minimun investment in USD (* 10**18) * @param _fundRaiseTypes Types of currency used to collect the funds * @param _wallet Ethereum account address to hold the funds * @param _reserveWallet Ethereum account address to receive unsold tokens */ function configure( uint256 _startTime, uint256 _endTime, uint256[] _ratePerTier, uint256[] _ratePerTierDiscountPoly, uint256[] _tokensPerTierTotal, uint256[] _tokensPerTierDiscountPoly, uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD, uint8[] _fundRaiseTypes, address _wallet, address _reserveWallet ) public onlyFactory { _configureFunding(_fundRaiseTypes); _configureAddresses(_wallet, _reserveWallet); _configureTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); _configureTimes(_startTime, _endTime); _configureLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); } function modifyFunding(uint8[] _fundRaiseTypes) public onlyOwner { require(now < startTime); _configureFunding(_fundRaiseTypes); } function modifyLimits( uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD ) public onlyOwner { require(now < startTime); _configureLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); } function modifyTiers( uint256[] _ratePerTier, uint256[] _ratePerTierDiscountPoly, uint256[] _tokensPerTierTotal, uint256[] _tokensPerTierDiscountPoly ) public onlyOwner { require(now < startTime); _configureTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); } function modifyTimes( uint256 _startTime, uint256 _endTime ) public onlyOwner { require(now < startTime); _configureTimes(_startTime, _endTime); } function modifyAddresses( address _wallet, address _reserveWallet ) public onlyOwner { require(now < startTime); _configureAddresses(_wallet, _reserveWallet); } function _configureFunding(uint8[] _fundRaiseTypes) internal { require(_fundRaiseTypes.length > 0 && _fundRaiseTypes.length < 3, "No fund raising currencies specified"); fundRaiseType[uint8(FundRaiseType.POLY)] = false; fundRaiseType[uint8(FundRaiseType.ETH)] = false; for (uint8 j = 0; j < _fundRaiseTypes.length; j++) { require(_fundRaiseTypes[j] < 2); fundRaiseType[_fundRaiseTypes[j]] = true; } emit SetFunding(_fundRaiseTypes); } function _configureLimits( uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD ) internal { minimumInvestmentUSD = _minimumInvestmentUSD; nonAccreditedLimitUSD = _nonAccreditedLimitUSD; emit SetLimits(minimumInvestmentUSD, nonAccreditedLimitUSD); } function _configureTiers( uint256[] _ratePerTier, uint256[] _ratePerTierDiscountPoly, uint256[] _tokensPerTierTotal, uint256[] _tokensPerTierDiscountPoly ) internal { require(_tokensPerTierTotal.length > 0); require(_ratePerTier.length == _tokensPerTierTotal.length, "Mismatch between rates and tokens per tier"); require(_ratePerTierDiscountPoly.length == _tokensPerTierTotal.length, "Mismatch between discount rates and tokens per tier"); require(_tokensPerTierDiscountPoly.length == _tokensPerTierTotal.length, "Mismatch between discount tokens per tier and tokens per tier"); for (uint8 i = 0; i < _ratePerTier.length; i++) { require(_ratePerTier[i] > 0, "Rate of token should be greater than 0"); require(_tokensPerTierTotal[i] > 0, "Tokens per tier should be greater than 0"); require(_tokensPerTierDiscountPoly[i] <= _tokensPerTierTotal[i], "Discounted tokens per tier should be less than or equal to tokens per tier"); require(_ratePerTierDiscountPoly[i] <= _ratePerTier[i], "Discounted rate per tier should be less than or equal to rate per tier"); } mintedPerTierTotal = new uint256[](_ratePerTier.length); mintedPerTierETH = new uint256[](_ratePerTier.length); mintedPerTierRegularPoly = new uint256[](_ratePerTier.length); mintedPerTierDiscountPoly = new uint256[](_ratePerTier.length); ratePerTier = _ratePerTier; ratePerTierDiscountPoly = _ratePerTierDiscountPoly; tokensPerTierTotal = _tokensPerTierTotal; tokensPerTierDiscountPoly = _tokensPerTierDiscountPoly; emit SetTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); } function _configureTimes( uint256 _startTime, uint256 _endTime ) internal { require(_endTime > _startTime, "Date parameters are not valid"); require(_startTime > now, "Start Time must be in the future"); startTime = _startTime; endTime = _endTime; emit SetTimes(_startTime, _endTime); } function _configureAddresses( address _wallet, address _reserveWallet ) internal { require(_wallet != address(0), "Zero address is not permitted for wallet"); require(_reserveWallet != address(0), "Zero address is not permitted for wallet"); wallet = _wallet; reserveWallet = _reserveWallet; emit SetAddresses(_wallet, _reserveWallet); } //////////////////// // STO Management // //////////////////// /** * @notice Finalize the STO and mint remaining tokens to reserve address * @notice Reserve address must be whitelisted to successfully finalize */ function finalize() public onlyOwner { require(!isFinalized); isFinalized = true; uint256 tempReturned; uint256 tempSold; uint256 remainingTokens; for (uint8 i = 0; i < tokensPerTierTotal.length; i++) { remainingTokens = tokensPerTierTotal[i].sub(mintedPerTierTotal[i]); tempReturned = tempReturned.add(remainingTokens); tempSold = tempSold.add(mintedPerTierTotal[i]); if (remainingTokens > 0) { mintedPerTierTotal[i] = tokensPerTierTotal[i]; require(IST20(securityToken).mint(reserveWallet, remainingTokens), "Error in minting the tokens"); emit ReserveTokenMint(msg.sender, reserveWallet, remainingTokens, i); } } finalAmountReturned = tempReturned; finalAmountSold = tempSold; } /** * @notice Modify the list of accredited addresses * @param _investors Array of investor addresses to modify * @param _accredited Array of bools specifying accreditation status */ function changeAccredited(address[] _investors, bool[] _accredited) public onlyOwner { require(_investors.length == _accredited.length); for (uint256 i = 0; i < _investors.length; i++) { accredited[_investors[i]] = _accredited[i]; } } ////////////////////////// // Investment Functions // ////////////////////////// /** * @notice fallback function - assumes ETH being invested */ function () external payable { buyWithETH(msg.sender); } /** * @notice Purchase tokens using ETH * @param _beneficiary Address where security tokens will be sent */ function buyWithETH(address _beneficiary) public payable validETH { uint256 rate = IOracle(ISecurityTokenRegistry(RegistryUpdater(securityToken).securityTokenRegistry()).getOracle(bytes32("ETH"), bytes32("USD"))).getPrice(); (uint256 spentUSD, uint256 spentValue) = _buyTokens(_beneficiary, msg.value, rate, false); // Modify storage investorInvestedETH[_beneficiary] = investorInvestedETH[_beneficiary].add(spentValue); fundsRaisedETH = fundsRaisedETH.add(spentValue); // Forward ETH to issuer wallet wallet.transfer(spentValue); // Refund excess ETH to investor wallet msg.sender.transfer(msg.value.sub(spentValue)); emit FundsReceivedETH(msg.sender, _beneficiary, spentUSD, msg.value, spentValue, rate); } /** * @notice Purchase tokens using POLY * @param _beneficiary Address where security tokens will be sent * @param _investedPOLY Amount of POLY invested */ function buyWithPOLY(address _beneficiary, uint256 _investedPOLY) public validPOLY { uint256 rate = IOracle(ISecurityTokenRegistry(RegistryUpdater(securityToken).securityTokenRegistry()).getOracle(bytes32("POLY"), bytes32("USD"))).getPrice(); (uint256 spentUSD, uint256 spentValue) = _buyTokens(_beneficiary, _investedPOLY, rate, true); // Modify storage investorInvestedPOLY[_beneficiary] = investorInvestedPOLY[_beneficiary].add(spentValue); fundsRaisedPOLY = fundsRaisedPOLY.add(spentValue); // Forward POLY to issuer wallet require(polyToken.transferFrom(msg.sender, wallet, spentValue)); emit FundsReceivedPOLY(msg.sender, _beneficiary, spentUSD, _investedPOLY, spentValue, rate); } /** * @notice Low level token purchase * @param _beneficiary Address where security tokens will be sent * @param _investmentValue Amount of POLY or ETH invested * @param _isPOLY Investment method */ function _buyTokens(address _beneficiary, uint256 _investmentValue, uint256 _rate, bool _isPOLY) internal nonReentrant whenNotPaused returns(uint256, uint256) { require(isOpen(), "STO is not open"); require(_investmentValue > 0, "No funds were sent to buy tokens"); uint256 investedUSD = decimalMul(_rate, _investmentValue); uint256 originalUSD = investedUSD; // Check for minimum investment require(investedUSD.add(investorInvestedUSD[_beneficiary]) >= minimumInvestmentUSD, "Total investment less than minimumInvestmentUSD"); // Check for non-accredited cap if (!accredited[_beneficiary]) { require(investorInvestedUSD[_beneficiary] < nonAccreditedLimitUSD, "Non-accredited investor has already reached nonAccreditedLimitUSD"); if (investedUSD.add(investorInvestedUSD[_beneficiary]) > nonAccreditedLimitUSD) investedUSD = nonAccreditedLimitUSD.sub(investorInvestedUSD[_beneficiary]); } uint256 spentUSD; // Iterate over each tier and process payment for (uint8 i = currentTier; i < ratePerTier.length; i++) { // Update current tier if needed if (currentTier != i) currentTier = i; // If there are tokens remaining, process investment if (mintedPerTierTotal[i] < tokensPerTierTotal[i]) spentUSD = spentUSD.add(_calculateTier(_beneficiary, i, investedUSD.sub(spentUSD), _isPOLY)); // If all funds have been spent, exit the loop if (investedUSD == spentUSD) break; } // Modify storage if (spentUSD > 0) { if (investorInvestedUSD[_beneficiary] == 0) investorCount = investorCount + 1; investorInvestedUSD[_beneficiary] = investorInvestedUSD[_beneficiary].add(spentUSD); fundsRaisedUSD = fundsRaisedUSD.add(spentUSD); } // Calculate spent in base currency (ETH or POLY) uint256 spentValue; if (spentUSD == 0) { spentValue = 0; } else { spentValue = decimalMul(decimalDiv(spentUSD, originalUSD), _investmentValue); } // Return calculated amounts return (spentUSD, spentValue); } function _calculateTier(address _beneficiary, uint8 _tier, uint256 _investedUSD, bool _isPOLY) internal returns(uint256) { // First purchase any discounted tokens if POLY investment uint256 spentUSD; uint256 tierSpentUSD; uint256 tierPurchasedTokens; // Check whether there are any remaining discounted tokens if (_isPOLY && tokensPerTierDiscountPoly[_tier] > mintedPerTierDiscountPoly[_tier]) { uint256 discountRemaining = tokensPerTierDiscountPoly[_tier].sub(mintedPerTierDiscountPoly[_tier]); uint256 totalRemaining = tokensPerTierTotal[_tier].sub(mintedPerTierTotal[_tier]); if (totalRemaining < discountRemaining) (spentUSD, tierPurchasedTokens) = _purchaseTier(_beneficiary, ratePerTierDiscountPoly[_tier], totalRemaining, _investedUSD, _tier); else (spentUSD, tierPurchasedTokens) = _purchaseTier(_beneficiary, ratePerTierDiscountPoly[_tier], discountRemaining, _investedUSD, _tier); _investedUSD = _investedUSD.sub(spentUSD); mintedPerTierDiscountPoly[_tier] = mintedPerTierDiscountPoly[_tier].add(tierPurchasedTokens); mintedPerTierTotal[_tier] = mintedPerTierTotal[_tier].add(tierPurchasedTokens); } // Now, if there is any remaining USD to be invested, purchase at non-discounted rate if ((_investedUSD > 0) && (tokensPerTierTotal[_tier].sub(mintedPerTierTotal[_tier]) > 0)) { (tierSpentUSD, tierPurchasedTokens) = _purchaseTier(_beneficiary, ratePerTier[_tier], tokensPerTierTotal[_tier].sub(mintedPerTierTotal[_tier]), _investedUSD, _tier); spentUSD = spentUSD.add(tierSpentUSD); if (_isPOLY) mintedPerTierRegularPoly[_tier] = mintedPerTierRegularPoly[_tier].add(tierPurchasedTokens); else mintedPerTierETH[_tier] = mintedPerTierETH[_tier].add(tierPurchasedTokens); mintedPerTierTotal[_tier] = mintedPerTierTotal[_tier].add(tierPurchasedTokens); } return spentUSD; } function _purchaseTier(address _beneficiary, uint256 _tierPrice, uint256 _tierRemaining, uint256 _investedUSD, uint8 _tier) internal returns(uint256, uint256) { uint256 maximumTokens = decimalDiv(_investedUSD, _tierPrice); uint256 spentUSD; uint256 purchasedTokens; if (maximumTokens > _tierRemaining) { spentUSD = decimalMul(_tierRemaining, _tierPrice); // In case of rounding issues, ensure that spentUSD is never more than investedUSD if (spentUSD > _investedUSD) { spentUSD = _investedUSD; } purchasedTokens = _tierRemaining; } else { spentUSD = _investedUSD; purchasedTokens = maximumTokens; } require(IST20(securityToken).mint(_beneficiary, purchasedTokens), "Error in minting the tokens"); emit TokenPurchase(msg.sender, _beneficiary, purchasedTokens, spentUSD, _tierPrice, _tier); return (spentUSD, purchasedTokens); } ///////////// // Getters // ///////////// /** * @notice This function returns whether or not the STO is in fundraising mode (open) * @return bool Whether the STO is accepting investments */ function isOpen() public view returns(bool) { if (isFinalized) return false; if (now < startTime) return false; if (now >= endTime) return false; if (capReached()) return false; return true; } /** * @notice This function converts from ETH or POLY to USD * @param _currency Currency key * @param _amount Value to convert to USD * @return uint256 Value in USD */ function convertToUSD(bytes32 _currency, uint256 _amount) public view returns(uint256) { uint256 rate = IOracle(ISecurityTokenRegistry(RegistryUpdater(securityToken).securityTokenRegistry()).getOracle(_currency, bytes32("USD"))).getPrice(); return decimalMul(_amount, rate); } /** * @notice This function converts from USD to ETH or POLY * @param _currency Currency key * @param _amount Value to convert from USD * @return uint256 Value in ETH or POLY */ function convertFromUSD(bytes32 _currency, uint256 _amount) public view returns(uint256) { uint256 rate = IOracle(ISecurityTokenRegistry(RegistryUpdater(securityToken).securityTokenRegistry()).getOracle(_currency, bytes32("USD"))).getPrice(); return decimalDiv(_amount, rate); } /** * @notice Checks whether the cap has been reached. * @return bool Whether the cap was reached */ function capReached() public view returns (bool) { if (isFinalized) { return (finalAmountReturned == 0); } return (mintedPerTierTotal[mintedPerTierTotal.length - 1] == tokensPerTierTotal[tokensPerTierTotal.length - 1]); } /** * @notice Return ETH raised by the STO * @return uint256 Amount of ETH raised */ function getRaisedEther() public view returns (uint256) { return fundsRaisedETH; } /** * @notice Return POLY raised by the STO * @return uint256 Amount of POLY raised */ function getRaisedPOLY() public view returns (uint256) { return fundsRaisedPOLY; } /** * @notice Return USD raised by the STO * @return uint256 Amount of USD raised */ function getRaisedUSD() public view returns (uint256) { return fundsRaisedUSD; } /** * @notice Return the total no. of investors * @return uint256 Investor count */ function getNumberInvestors() public view returns (uint256) { return investorCount; } /** * @notice Return the total no. of tokens sold * @return uint256 Total number of tokens sold */ function getTokensSold() public view returns (uint256) { if (isFinalized) return finalAmountSold; else return getTokensMinted(); } /** * @notice Return the total no. of tokens minted * @return uint256 Total number of tokens minted */ function getTokensMinted() public view returns (uint256) { uint256 tokensMinted; for (uint8 i = 0; i < mintedPerTierTotal.length; i++) { tokensMinted = tokensMinted.add(mintedPerTierTotal[i]); } return tokensMinted; } /** * @notice Return the total no. of tokens sold for ETH * @return uint256 Total number of tokens sold for ETH */ function getTokensSoldForETH() public view returns (uint256) { uint256 tokensSold; for (uint8 i = 0; i < mintedPerTierETH.length; i++) { tokensSold = tokensSold.add(mintedPerTierETH[i]); } return tokensSold; } /** * @notice Return the total no. of tokens sold for POLY * @return uint256 Total number of tokens sold for POLY */ function getTokensSoldForPOLY() public view returns (uint256) { uint256 tokensSold; for (uint8 i = 0; i < mintedPerTierRegularPoly.length; i++) { tokensSold = tokensSold.add(mintedPerTierRegularPoly[i]).add(mintedPerTierDiscountPoly[i]); } return tokensSold; } /** * @notice Return the total no. of tiers * @return uint256 Total number of tiers */ function getNumberOfTiers() public view returns (uint256) { return tokensPerTierTotal.length; } /** * @notice Return the permissions flag that are associated with STO */ function getPermissions() public view returns(bytes32[]) { bytes32[] memory allPermissions = new bytes32[](0); return allPermissions; } /** * @notice This function returns the signature of configure function * @return bytes4 Configure function signature */ function getInitFunction() public pure returns (bytes4) { return bytes4(keccak256("configure(uint256,uint256,uint256[],uint256[],uint256[],uint256[],uint256,uint256,uint8[],address,address)")); } uint constant DECIMALS = 10 ** 18; /** * @notice This function multiplies two decimals represented as (decimal * 10**DECIMALS) * @return uint256 Result of multiplication represented as (decimal * 10**DECIMALS) */ function decimalMul(uint256 x, uint256 y) internal pure returns (uint256 z) { z = SafeMath.add(SafeMath.mul(x, y), DECIMALS / 2) / DECIMALS; } /** * @notice This function divides two decimals represented as (decimal * 10**DECIMALS) * @return uint256 Result of division represented as (decimal * 10**DECIMALS) */ function decimalDiv(uint256 x, uint256 y) internal pure returns (uint256 z) { z = SafeMath.add(SafeMath.mul(x, DECIMALS), y / 2) / y; } }