diff --git a/contracts/interfaces/ISTO.sol b/contracts/interfaces/ISTO.sol new file mode 100644 index 000000000..dd5ce3f16 --- /dev/null +++ b/contracts/interfaces/ISTO.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.24; + +/** + * @title Interface to be implemented by all STO modules + */ +interface ISTO { + /** + * @notice Returns the total no. of tokens sold + */ + function getTokensSold() external view returns (uint256); +} diff --git a/contracts/modules/STO/CappedSTO.sol b/contracts/modules/STO/CappedSTO.sol index af091dacf..3245aa3f5 100644 --- a/contracts/modules/STO/CappedSTO.sol +++ b/contracts/modules/STO/CappedSTO.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; @@ -8,7 +8,7 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title STO module for standard capped crowdsale */ -contract CappedSTO is ISTO, ReentrancyGuard { +contract CappedSTO is STO, ReentrancyGuard { using SafeMath for uint256; // Determine whether users can invest on behalf of a beneficiary @@ -51,7 +51,7 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @param _startTime Unix timestamp at which offering get started * @param _endTime Unix timestamp at which offering get ended * @param _cap Maximum No. of token base units for sale - * @param _rate Token units a buyer gets multiplied by 10^18 per wei / base unit of POLY + * @param _rate Token units a buyer gets multiplied by 10^18 per wei / base unit of POLY * @param _fundRaiseTypes Type of currency used to collect the funds * @param _fundsReceiver Ethereum account address to hold the funds */ @@ -115,7 +115,6 @@ contract CappedSTO is ISTO, ReentrancyGuard { weiAmount = weiAmount.sub(refund); _forwardFunds(refund); - _postValidatePurchase(_beneficiary, weiAmount); } /** @@ -127,7 +126,6 @@ contract CappedSTO is ISTO, ReentrancyGuard { require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Mode of investment is not POLY"); uint256 refund = _processTx(msg.sender, _investedPOLY); _forwardPoly(msg.sender, wallet, _investedPOLY.sub(refund)); - _postValidatePurchase(msg.sender, _investedPOLY.sub(refund)); } /** @@ -161,7 +159,7 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @return Token units a buyer gets(multiplied by 10^18) per wei / base unit of POLY * @return Amount of funds raised * @return Number of individual investors this STO have. - * @return Amount of tokens get sold. + * @return Amount of tokens get sold. * @return Boolean value to justify whether the fund raise type is POLY or not, i.e true for POLY. */ function getSTODetails() public view returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool) { @@ -203,8 +201,6 @@ contract CappedSTO is ISTO, ReentrancyGuard { _processPurchase(_beneficiary, tokens); emit TokenPurchase(msg.sender, _beneficiary, _investedAmount, tokens); - - _updatePurchasingState(_beneficiary, _investedAmount); } /** @@ -216,21 +212,10 @@ contract CappedSTO is ISTO, ReentrancyGuard { function _preValidatePurchase(address _beneficiary, uint256 _investedAmount) internal view { require(_beneficiary != address(0), "Beneficiary address should not be 0x"); require(_investedAmount != 0, "Amount invested should not be equal to 0"); - uint256 tokens; - (tokens, ) = _getTokenAmount(_investedAmount); - require(totalTokensSold.add(tokens) <= cap, "Investment more than cap is not allowed"); /*solium-disable-next-line security/no-block-members*/ require(now >= startTime && now <= endTime, "Offering is closed/Not yet started"); } - /** - * @notice Validation of an executed purchase. - Observe state and use revert statements to undo rollback when valid conditions are not met. - */ - function _postValidatePurchase(address /*_beneficiary*/, uint256 /*_investedAmount*/) internal pure { - // optional override - } - /** * @notice Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends its tokens. @@ -255,27 +240,23 @@ contract CappedSTO is ISTO, ReentrancyGuard { _deliverTokens(_beneficiary, _tokenAmount); } - /** - * @notice Overrides for extensions that require an internal state to check for validity - (current user contributions, etc.) - */ - function _updatePurchasingState(address /*_beneficiary*/, uint256 /*_investedAmount*/) internal pure { - // optional override - } - /** * @notice Overrides to extend the way in which ether is converted to tokens. * @param _investedAmount Value in wei to be converted into tokens * @return Number of tokens that can be purchased with the specified _investedAmount * @return Remaining amount that should be refunded to the investor */ - function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 _tokens, uint256 _refund) { - _tokens = _investedAmount.mul(rate); - _tokens = _tokens.div(uint256(10) ** 18); + function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 tokens, uint256 refund) { + tokens = _investedAmount.mul(rate); + tokens = tokens.div(uint256(10) ** 18); + if (totalTokensSold.add(tokens) > cap) { + tokens = cap.sub(totalTokensSold); + } uint256 granularity = ISecurityToken(securityToken).granularity(); - _tokens = _tokens.div(granularity); - _tokens = _tokens.mul(granularity); - _refund = _investedAmount.sub((_tokens.mul(uint256(10) ** 18)).div(rate)); + tokens = tokens.div(granularity); + tokens = tokens.mul(granularity); + require(tokens > 0, "Cap reached"); + refund = _investedAmount.sub((tokens.mul(uint256(10) ** 18)).div(rate)); } /** diff --git a/contracts/modules/STO/DummySTO.sol b/contracts/modules/STO/DummySTO.sol index 1b44d2e9b..fafc6c1c4 100644 --- a/contracts/modules/STO/DummySTO.sol +++ b/contracts/modules/STO/DummySTO.sol @@ -1,12 +1,12 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; /** * @title STO module for sample implementation of a different crowdsale module */ -contract DummySTO is ISTO { +contract DummySTO is STO { bytes32 public constant ADMIN = "ADMIN"; diff --git a/contracts/modules/STO/PreSaleSTO.sol b/contracts/modules/STO/PreSaleSTO.sol index 7378fb06b..4dacaa2d7 100644 --- a/contracts/modules/STO/PreSaleSTO.sol +++ b/contracts/modules/STO/PreSaleSTO.sol @@ -1,13 +1,13 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title STO module for private presales */ -contract PreSaleSTO is ISTO { +contract PreSaleSTO is STO { using SafeMath for uint256; bytes32 public constant PRE_SALE_ADMIN = "PRE_SALE_ADMIN"; diff --git a/contracts/modules/STO/ISTO.sol b/contracts/modules/STO/STO.sol similarity index 87% rename from contracts/modules/STO/ISTO.sol rename to contracts/modules/STO/STO.sol index 6fb216566..5f9a6e9e3 100644 --- a/contracts/modules/STO/ISTO.sol +++ b/contracts/modules/STO/STO.sol @@ -3,17 +3,18 @@ pragma solidity ^0.4.24; import "../../Pausable.sol"; import "../Module.sol"; import "../../interfaces/IERC20.sol"; -import "./ISTOStorage.sol"; +import "../../interfaces/ISTO.sol"; +import "./STOStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title Interface to be implemented by all STO modules */ -contract ISTO is ISTOStorage, Module, Pausable { +contract STO is ISTO, STOStorage, Module, Pausable { using SafeMath for uint256; enum FundRaiseType { ETH, POLY, SC } - + // Event event SetFundRaiseTypes(FundRaiseType[] _fundRaiseTypes); @@ -36,11 +37,6 @@ contract ISTO is ISTOStorage, Module, Pausable { return fundsRaised[uint8(_fundRaiseType)]; } - /** - * @notice Returns the total no. of tokens sold - */ - function getTokensSold() public view returns (uint256); - /** * @notice Pause (overridden function) */ @@ -59,7 +55,7 @@ contract ISTO is ISTOStorage, Module, Pausable { function _setFundRaiseType(FundRaiseType[] _fundRaiseTypes) internal { // FundRaiseType[] parameter type ensures only valid values for _fundRaiseTypes - require(_fundRaiseTypes.length > 0, "Raise type is not specified"); + require(_fundRaiseTypes.length > 0 && _fundRaiseTypes.length <= 3, "Raise type is not specified"); fundRaiseTypes[uint8(FundRaiseType.ETH)] = false; fundRaiseTypes[uint8(FundRaiseType.POLY)] = false; fundRaiseTypes[uint8(FundRaiseType.SC)] = false; diff --git a/contracts/modules/STO/ISTOStorage.sol b/contracts/modules/STO/STOStorage.sol similarity index 88% rename from contracts/modules/STO/ISTOStorage.sol rename to contracts/modules/STO/STOStorage.sol index b808c98b4..2e2401fb0 100644 --- a/contracts/modules/STO/ISTOStorage.sol +++ b/contracts/modules/STO/STOStorage.sol @@ -1,9 +1,9 @@ pragma solidity ^0.4.24; /** - * @title Storage layout for the ISTO contract + * @title Storage layout for the STO contract */ -contract ISTOStorage { +contract STOStorage { mapping (uint8 => bool) public fundRaiseTypes; mapping (uint8 => uint256) public fundsRaised; @@ -21,4 +21,4 @@ contract ISTOStorage { // Final amount of tokens sold uint256 public totalTokensSold; -} \ No newline at end of file +} diff --git a/contracts/modules/STO/USDTieredSTO.sol b/contracts/modules/STO/USDTieredSTO.sol index f1c959671..c29402355 100644 --- a/contracts/modules/STO/USDTieredSTO.sol +++ b/contracts/modules/STO/USDTieredSTO.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; import "../../interfaces/IOracle.sol"; import "../../RegistryUpdater.sol"; @@ -12,7 +12,7 @@ import "../../storage/USDTieredSTOStorage.sol"; /** * @title STO module for standard capped crowdsale */ -contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { +contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { using SafeMath for uint256; string public constant POLY_ORACLE = "PolyUsdOracle"; @@ -262,7 +262,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { } usdTokens = _usdTokens; for(i = 0; i < _usdTokens.length; i++) { - require(_usdTokens[i] != address(0), "Invalid USD token"); + require(_usdTokens[i] != address(0) && _usdTokens[i] != address(polyToken), "Invalid USD token"); usdTokenEnabled[_usdTokens[i]] = true; } emit SetAddresses(wallet, reserveWallet, _usdTokens); @@ -276,7 +276,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @notice Finalizes the STO and mint remaining tokens to reserve address * @notice Reserve address must be whitelisted to successfully finalize */ - function finalize() public onlyOwner { + function finalize() external onlyOwner { require(!isFinalized, "STO is already finalized"); isFinalized = true; uint256 tempReturned; @@ -301,7 +301,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @param _investors Array of investor addresses to modify * @param _accredited Array of bools specifying accreditation status */ - function changeAccredited(address[] _investors, bool[] _accredited) public onlyOwner { + function changeAccredited(address[] _investors, bool[] _accredited) external onlyOwner { require(_investors.length == _accredited.length, "Array length mismatch"); for (uint256 i = 0; i < _investors.length; i++) { if (_accredited[i]) { @@ -319,7 +319,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @param _investors Array of investor addresses to modify * @param _nonAccreditedLimit Array of uints specifying non-accredited limits */ - function changeNonAccreditedLimit(address[] _investors, uint256[] _nonAccreditedLimit) public onlyOwner { + function changeNonAccreditedLimit(address[] _investors, uint256[] _nonAccreditedLimit) external onlyOwner { //nonAccreditedLimitUSDOverride require(_investors.length == _nonAccreditedLimit.length, "Array length mismatch"); for (uint256 i = 0; i < _investors.length; i++) { @@ -357,7 +357,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @notice Function to set allowBeneficialInvestments (allow beneficiary to be different to funder) * @param _allowBeneficialInvestments Boolean to allow or disallow beneficial investments */ - function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) public onlyOwner { + function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) external onlyOwner { require(_allowBeneficialInvestments != allowBeneficialInvestments, "Value unchanged"); allowBeneficialInvestments = _allowBeneficialInvestments; emit SetAllowBeneficialInvestments(allowBeneficialInvestments); @@ -508,7 +508,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { uint256 _investmentValue, FundRaiseType _fundRaiseType ) - public + external view returns(uint256 spentUSD, uint256 spentValue, uint256 tokensMinted) { @@ -732,7 +732,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @param _amount Value to convert to USD * @return uint256 Value in USD */ - function convertToUSD(FundRaiseType _fundRaiseType, uint256 _amount) public view returns(uint256) { + function convertToUSD(FundRaiseType _fundRaiseType, uint256 _amount) external view returns(uint256) { uint256 rate = getRate(_fundRaiseType); return DecimalMath.mul(_amount, rate); } @@ -743,7 +743,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @param _amount Value to convert from USD * @return uint256 Value in ETH or POLY */ - function convertFromUSD(FundRaiseType _fundRaiseType, uint256 _amount) public view returns(uint256) { + function convertFromUSD(FundRaiseType _fundRaiseType, uint256 _amount) external view returns(uint256) { uint256 rate = getRate(_fundRaiseType); return DecimalMath.div(_amount, rate); } @@ -776,7 +776,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * param _fundRaiseType The fund raising currency (e.g. ETH, POLY, SC) to calculate sold tokens for * @return uint256 Total number of tokens sold for ETH */ - function getTokensSoldFor(FundRaiseType _fundRaiseType) public view returns (uint256) { + function getTokensSoldFor(FundRaiseType _fundRaiseType) external view returns (uint256) { uint256 tokensSold; for (uint256 i = 0; i < tiers.length; i++) { tokensSold = tokensSold.add(tiers[i].minted[uint8(_fundRaiseType)]); @@ -789,7 +789,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * param _tier The tier to return minted tokens for * @return uint256[] array of minted tokens in each fund raise type */ - function getTokensMintedByTier(uint256 _tier) public view returns (uint256[]) { + function getTokensMintedByTier(uint256 _tier) external view returns (uint256[]) { require(_tier < tiers.length, "Invalid tier"); uint256[] memory tokensMinted = new uint256[](3); tokensMinted[0] = tiers[_tier].minted[uint8(FundRaiseType.ETH)]; @@ -803,7 +803,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * param _tier The tier to calculate sold tokens for * @return uint256 Total number of tokens sold in the tier */ - function getTokensSoldByTier(uint256 _tier) public view returns (uint256) { + function getTokensSoldByTier(uint256 _tier) external view returns (uint256) { require(_tier < tiers.length, "Incorrect tier"); uint256 tokensSold; tokensSold = tokensSold.add(tiers[_tier].minted[uint8(FundRaiseType.ETH)]); @@ -816,7 +816,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @notice Return the total no. of tiers * @return uint256 Total number of tiers */ - function getNumberOfTiers() public view returns (uint256) { + function getNumberOfTiers() external view returns (uint256) { return tiers.length; } @@ -824,7 +824,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @notice Return the usd tokens accepted by the STO * @return address[] usd tokens */ - function getUsdTokens() public view returns (address[]) { + function getUsdTokens() external view returns (address[]) { return usdTokens; } @@ -848,7 +848,7 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { * @return Amount of tokens sold. * @return Array of bools to show if funding is allowed in ETH, POLY, SC respectively */ - function getSTODetails() public view returns(uint256, uint256, uint256, uint256[], uint256[], uint256, uint256, uint256, bool[]) { + function getSTODetails() external view returns(uint256, uint256, uint256, uint256[], uint256[], uint256, uint256, uint256, bool[]) { uint256[] memory cap = new uint256[](tiers.length); uint256[] memory rate = new uint256[](tiers.length); for(uint256 i = 0; i < tiers.length; i++) { diff --git a/contracts/proxy/USDTieredSTOProxy.sol b/contracts/proxy/USDTieredSTOProxy.sol index a412a8278..050e14a21 100644 --- a/contracts/proxy/USDTieredSTOProxy.sol +++ b/contracts/proxy/USDTieredSTOProxy.sol @@ -4,13 +4,13 @@ import "../storage/USDTieredSTOStorage.sol"; import "./OwnedProxy.sol"; import "../Pausable.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; -import "../modules/STO/ISTOStorage.sol"; +import "../modules/STO/STOStorage.sol"; import "../modules/ModuleStorage.sol"; /** * @title USDTiered STO module Proxy */ -contract USDTieredSTOProxy is USDTieredSTOStorage, ISTOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedProxy { +contract USDTieredSTOProxy is USDTieredSTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedProxy { /** * @notice Constructor diff --git a/contracts/storage/USDTieredSTOStorage.sol b/contracts/storage/USDTieredSTOStorage.sol index f801000e0..31e9d803d 100644 --- a/contracts/storage/USDTieredSTOStorage.sol +++ b/contracts/storage/USDTieredSTOStorage.sol @@ -51,9 +51,6 @@ contract USDTieredSTOStorage { // Whether or not the STO has been finalized bool public isFinalized; - // Address where ETH, POLY & Stable Coin funds are delivered - address public wallet; - // Address of issuer reserve wallet for unsold tokens address public reserveWallet; diff --git a/test/p_usd_tiered_sto.js b/test/p_usd_tiered_sto.js index ae1fbbbb0..fca953a18 100644 --- a/test/p_usd_tiered_sto.js +++ b/test/p_usd_tiered_sto.js @@ -1246,7 +1246,8 @@ contract("USDTieredSTO", accounts => { await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 }); // Change Stable coin address - await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, RESERVEWALLET, [I_PolyToken.address], { from: ISSUER }); + let I_DaiToken2 = await PolyTokenFaucet.new({from: POLYMATH}); + await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, RESERVEWALLET, [I_DaiToken2.address], { from: ISSUER }); // NONACCREDITED DAI await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 }));