diff --git a/CHANGELOG.md b/CHANGELOG.md index 71457d9d9..89799ef74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,15 @@ All notable changes to this project will be documented in this file. ## CappedSTO 2.0.1 * `rate` is now accepted as multiplied by 10^18 to allow settting higher price than 1ETH/POLY per token. -## USDTieredSTO 2.0.1 +## USDTieredSTO 2.1.0 * Added `buyTokensView` and `getTokensMintedByTier` to USDTSTO. * Added `getSTODetails` to USDTSTO. * Added an Array of Tiers that will hold data about every tier in USDTSTO. * Added `buyWithETHRateLimited`, `buyWithPOLYRateLimited` and `buyWithUSDRateLimited` to USDTSTO. * Added `getTokensSoldByTier` to return sold (not minted during finalisation) tokens in each tier to USDTSTO. * Removed individual mappings for tier data removed in UDSTSTO. +* Removed the old Proxy deployment method of USDTieredSTO and adopt the new inherited proxy deployment approach. +* Bump the version to `2.1.0` ## GeneralTransferManager * `getInvestors`, `getAllInvestorsData`, `getInvestorsData` added to GTM to allow easy data queries. diff --git a/contracts/interfaces/IBoot.sol b/contracts/interfaces/IBoot.sol new file mode 100644 index 000000000..db29dc319 --- /dev/null +++ b/contracts/interfaces/IBoot.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.4.24; + +interface IBoot { + + /** + * @notice This function returns the signature of configure function + * @return bytes4 Configure function signature + */ + function getInitFunction() external pure returns(bytes4); +} \ No newline at end of file diff --git a/contracts/modules/STO/ISTO.sol b/contracts/modules/STO/ISTO.sol index 6ed1d5ba1..7c8e45852 100644 --- a/contracts/modules/STO/ISTO.sol +++ b/contracts/modules/STO/ISTO.sol @@ -3,30 +3,16 @@ pragma solidity ^0.4.24; import "../../Pausable.sol"; import "../Module.sol"; import "../../interfaces/IERC20.sol"; +import "./ISTOStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title Interface to be implemented by all STO modules */ -contract ISTO is Module, Pausable { +contract ISTO is ISTOStorage, Module, Pausable { using SafeMath for uint256; enum FundRaiseType { ETH, POLY, DAI } - mapping (uint8 => bool) public fundRaiseTypes; - mapping (uint8 => uint256) public fundsRaised; - - // Start time of the STO - uint256 public startTime; - // End time of the STO - uint256 public endTime; - // Time STO was paused - uint256 public pausedTime; - // Number of individual investors - uint256 public investorCount; - // Address where ETH & POLY funds are delivered - address public wallet; - // Final amount of tokens sold - uint256 public totalTokensSold; // Event event SetFundRaiseTypes(FundRaiseType[] _fundRaiseTypes); diff --git a/contracts/modules/STO/ISTOStorage.sol b/contracts/modules/STO/ISTOStorage.sol new file mode 100644 index 000000000..b808c98b4 --- /dev/null +++ b/contracts/modules/STO/ISTOStorage.sol @@ -0,0 +1,24 @@ +pragma solidity ^0.4.24; + +/** + * @title Storage layout for the ISTO contract + */ +contract ISTOStorage { + + mapping (uint8 => bool) public fundRaiseTypes; + mapping (uint8 => uint256) public fundsRaised; + + // Start time of the STO + uint256 public startTime; + // End time of the STO + uint256 public endTime; + // Time STO was paused + uint256 public pausedTime; + // Number of individual investors + uint256 public investorCount; + // Address where ETH & POLY funds are delivered + address public wallet; + // Final amount of tokens sold + uint256 public totalTokensSold; + +} \ No newline at end of file diff --git a/contracts/modules/STO/ProxyFactory/USDTieredSTOProxyFactory.sol b/contracts/modules/STO/ProxyFactory/USDTieredSTOProxyFactory.sol deleted file mode 100644 index d9a48b87d..000000000 --- a/contracts/modules/STO/ProxyFactory/USDTieredSTOProxyFactory.sol +++ /dev/null @@ -1,33 +0,0 @@ -pragma solidity ^0.4.24; - -import "../USDTieredSTO.sol"; -import "../../../interfaces/IUSDTieredSTOProxy.sol"; - -contract USDTieredSTOProxyFactory is IUSDTieredSTOProxy { - - constructor() public { - - } - - /** - * @notice Deploys the STO. - * @param _securityToken Contract address of the securityToken - * @param _polyAddress Contract address of the PolyToken. - * @param _factoryAddress Contract address of the factory - * @return address Address of the deployed STO - */ - function deploySTO(address _securityToken, address _polyAddress, address _factoryAddress) external returns (address) { - address newSecurityTokenAddress = new USDTieredSTO(_securityToken, _polyAddress, _factoryAddress); - return newSecurityTokenAddress; - } - - /** - * @notice Used to get the init function signature - * @param _contractAddress Address of the STO contract - * @return bytes4 - */ - function getInitFunction(address _contractAddress) external returns (bytes4) { - return USDTieredSTO(_contractAddress).getInitFunction(); - } - -} diff --git a/contracts/modules/STO/USDTieredSTO.sol b/contracts/modules/STO/USDTieredSTO.sol index baba70472..825df2122 100644 --- a/contracts/modules/STO/USDTieredSTO.sol +++ b/contracts/modules/STO/USDTieredSTO.sol @@ -7,86 +7,16 @@ import "../../RegistryUpdater.sol"; import "../../libraries/DecimalMath.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; +import "./USDTieredSTOStorage.sol"; /** * @title STO module for standard capped crowdsale */ -contract USDTieredSTO is ISTO, ReentrancyGuard { +contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard { using SafeMath for uint256; - ///////////// - // Storage // - ///////////// - struct Tier { - // How many token units a buyer gets per USD in this tier (multiplied by 10**18) - uint256 rate; - - // How many token units a buyer gets per USD in this tier (multiplied by 10**18) when investing in POLY up to tokensDiscountPoly - uint256 rateDiscountPoly; - - // How many tokens are available in this tier (relative to totalSupply) - uint256 tokenTotal; - - // How many token units are available in this tier (relative to totalSupply) at the ratePerTierDiscountPoly rate - uint256 tokensDiscountPoly; - - // How many tokens have been minted in this tier (relative to totalSupply) - uint256 mintedTotal; - - // How many tokens have been minted in this tier (relative to totalSupply) for each fund raise type - mapping (uint8 => uint256) minted; - - // How many tokens have been minted in this tier (relative to totalSupply) at discounted POLY rate - uint256 mintedDiscountPoly; - } - - string public POLY_ORACLE = "PolyUsdOracle"; - string public ETH_ORACLE = "EthUsdOracle"; - mapping (bytes32 => mapping (bytes32 => string)) oracleKeys; - - IERC20 public usdToken; - - // Determine whether users can invest on behalf of a beneficiary - bool public allowBeneficialInvestments = false; - - // Whether or not the STO has been finalized - bool public isFinalized; - - // Address where ETH, POLY & DAI funds are delivered - address public wallet; - - // Address of issuer reserve wallet for unsold tokens - address public reserveWallet; - - // Current tier - uint256 public currentTier; - - // Amount of USD funds raised - uint256 public fundsRaisedUSD; - - // Amount in USD invested by each address - mapping (address => uint256) public investorInvestedUSD; - - // Amount in fund raise type invested by each investor - mapping (address => mapping (uint8 => uint256)) public investorInvested; - - // List of accredited investors - mapping (address => bool) public accredited; - - // Default limit in USD for non-accredited investors multiplied by 10**18 - uint256 public nonAccreditedLimitUSD; - - // Overrides for default limit in USD for non-accredited investors multiplied by 10**18 - mapping (address => uint256) public nonAccreditedLimitUSDOverride; - - // Minimum investable amount in USD - uint256 public minimumInvestmentUSD; - - // Final amount of tokens returned to issuer - uint256 public finalAmountReturned; - - // Array of Tiers - Tier[] public tiers; + string public constant POLY_ORACLE = "PolyUsdOracle"; + string public constant ETH_ORACLE = "EthUsdOracle"; //////////// // Events // @@ -158,11 +88,10 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { // STO Configuration // /////////////////////// - constructor (address _securityToken, address _polyAddress, address _factory) public Module(_securityToken, _polyAddress) { - require(_factory != address(0), "Invalid factory"); - oracleKeys[bytes32("ETH")][bytes32("USD")] = ETH_ORACLE; - oracleKeys[bytes32("POLY")][bytes32("USD")] = POLY_ORACLE; - factory = _factory; //Explicitly setting factory as we are using proxy deployment for this module + constructor (address _securityToken, address _polyAddress) + public + Module(_securityToken, _polyAddress) + { } /** @@ -192,6 +121,8 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { address _reserveWallet, address _usdToken ) public onlyFactory { + oracleKeys[bytes32("ETH")][bytes32("USD")] = ETH_ORACLE; + oracleKeys[bytes32("POLY")][bytes32("USD")] = POLY_ORACLE; require(endTime == 0, "Already configured"); _modifyTimes(_startTime, _endTime); _modifyTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); diff --git a/contracts/modules/STO/USDTieredSTOFactory.sol b/contracts/modules/STO/USDTieredSTOFactory.sol index adc3ba628..3263a4fa7 100644 --- a/contracts/modules/STO/USDTieredSTOFactory.sol +++ b/contracts/modules/STO/USDTieredSTOFactory.sol @@ -1,6 +1,7 @@ pragma solidity ^0.4.24; -import "../../interfaces/IUSDTieredSTOProxy.sol"; +import "../../interfaces/IBoot.sol"; +import "../../proxy/USDTieredSTOProxy.sol"; import "../ModuleFactory.sol"; import "../../libraries/Util.sol"; @@ -9,18 +10,18 @@ import "../../libraries/Util.sol"; */ contract USDTieredSTOFactory is ModuleFactory { - address public USDTieredSTOProxyAddress; + address public logicContract; /** * @notice Constructor * @param _polyAddress Address of the polytoken */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _proxyFactoryAddress) public + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - require(_proxyFactoryAddress != address(0), "0x address is not allowed"); - USDTieredSTOProxyAddress = _proxyFactoryAddress; - version = "1.0.0"; + require(_logicContract != address(0), "0x address is not allowed"); + logicContract = _logicContract; + version = "2.1.0"; name = "USDTieredSTO"; title = "USD Tiered STO"; /*solium-disable-next-line max-len*/ @@ -36,11 +37,10 @@ contract USDTieredSTOFactory is ModuleFactory { function deploy(bytes _data) external returns(address) { if(setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "Sufficent Allowance is not provided"); - require(USDTieredSTOProxyAddress != address(0), "Proxy contract should be pre-set"); //Check valid bytes - can only call module init function - address usdTieredSTO = IUSDTieredSTOProxy(USDTieredSTOProxyAddress).deploySTO(msg.sender, address(polyToken), address(this)); + address usdTieredSTO = new USDTieredSTOProxy(msg.sender, address(polyToken), logicContract); //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == IUSDTieredSTOProxy(USDTieredSTOProxyAddress).getInitFunction(usdTieredSTO), "Invalid data"); + require(Util.getSig(_data) == IBoot(usdTieredSTO).getInitFunction(), "Invalid data"); /*solium-disable-next-line security/no-low-level-calls*/ require(address(usdTieredSTO).call(_data), "Unsuccessfull call"); /*solium-disable-next-line security/no-block-members*/ diff --git a/contracts/modules/STO/USDTieredSTOStorage.sol b/contracts/modules/STO/USDTieredSTOStorage.sol new file mode 100644 index 000000000..031792f42 --- /dev/null +++ b/contracts/modules/STO/USDTieredSTOStorage.sol @@ -0,0 +1,82 @@ +pragma solidity ^0.4.24; + +import "../../interfaces/IERC20.sol"; + +/** + * @title Contract used to store layout for the USDTieredSTO storage + */ +contract USDTieredSTOStorage { + + ///////////// + // Storage // + ///////////// + struct Tier { + // How many token units a buyer gets per USD in this tier (multiplied by 10**18) + uint256 rate; + + // How many token units a buyer gets per USD in this tier (multiplied by 10**18) when investing in POLY up to tokensDiscountPoly + uint256 rateDiscountPoly; + + // How many tokens are available in this tier (relative to totalSupply) + uint256 tokenTotal; + + // How many token units are available in this tier (relative to totalSupply) at the ratePerTierDiscountPoly rate + uint256 tokensDiscountPoly; + + // How many tokens have been minted in this tier (relative to totalSupply) + uint256 mintedTotal; + + // How many tokens have been minted in this tier (relative to totalSupply) for each fund raise type + mapping (uint8 => uint256) minted; + + // How many tokens have been minted in this tier (relative to totalSupply) at discounted POLY rate + uint256 mintedDiscountPoly; + } + + mapping (bytes32 => mapping (bytes32 => string)) oracleKeys; + + IERC20 public usdToken; + + // Determine whether users can invest on behalf of a beneficiary + bool public allowBeneficialInvestments = false; + + // Whether or not the STO has been finalized + bool public isFinalized; + + // Address where ETH, POLY & DAI funds are delivered + address public wallet; + + // Address of issuer reserve wallet for unsold tokens + address public reserveWallet; + + // Current tier + uint256 public currentTier; + + // Amount of USD funds raised + uint256 public fundsRaisedUSD; + + // Amount in USD invested by each address + mapping (address => uint256) public investorInvestedUSD; + + // Amount in fund raise type invested by each investor + mapping (address => mapping (uint8 => uint256)) public investorInvested; + + // List of accredited investors + mapping (address => bool) public accredited; + + // Default limit in USD for non-accredited investors multiplied by 10**18 + uint256 public nonAccreditedLimitUSD; + + // Overrides for default limit in USD for non-accredited investors multiplied by 10**18 + mapping (address => uint256) public nonAccreditedLimitUSDOverride; + + // Minimum investable amount in USD + uint256 public minimumInvestmentUSD; + + // Final amount of tokens returned to issuer + uint256 public finalAmountReturned; + + // Array of Tiers + Tier[] public tiers; + +} \ No newline at end of file diff --git a/contracts/proxy/USDTieredSTOProxy.sol b/contracts/proxy/USDTieredSTOProxy.sol new file mode 100644 index 000000000..12cf8fdb1 --- /dev/null +++ b/contracts/proxy/USDTieredSTOProxy.sol @@ -0,0 +1,32 @@ +pragma solidity ^0.4.24; + +import "../modules/STO/USDTieredSTOStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; +import "../modules/STO/ISTOStorage.sol"; +import "../modules/ModuleStorage.sol"; + +/** + * @title USDTiered STO module Proxy + */ +contract USDTieredSTOProxy is USDTieredSTOStorage, ISTOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor (address _securityToken, address _polyAddress, address _implementation) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + __implementation = _implementation; + } + +} diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index baa2ae687..0a5c79341 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -3,7 +3,7 @@ const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManage const GeneralTransferManagerLogic = artifacts.require('./GeneralTransferManager.sol') const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol') const PercentageTransferManagerFactory = artifacts.require('./PercentageTransferManagerFactory.sol') -const USDTieredSTOProxyFactory = artifacts.require('./USDTieredSTOProxyFactory.sol'); +const USDTieredSTOLogic = artifacts.require('./USDTieredSTO.sol'); const CountTransferManagerFactory = artifacts.require('./CountTransferManagerFactory.sol') const EtherDividendCheckpointLogic = artifacts.require('./EtherDividendCheckpoint.sol') const ERC20DividendCheckpointLogic = artifacts.require('./ERC20DividendCheckpoint.sol') @@ -161,6 +161,10 @@ module.exports = function (deployer, network, accounts) { // B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this // manager attach with the securityToken contract at the time of deployment) return deployer.deploy(EtherDividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); + }).then(() => { + // B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); }).then(() => { // B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this // manager attach with the securityToken contract at the time of deployment) @@ -287,12 +291,9 @@ module.exports = function (deployer, network, accounts) { // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. return moduleRegistry.verifyModule(CappedSTOFactory.address, true, {from: PolymathAccount}) - }).then(() => { - // Deploy the proxy factory - return deployer.deploy(USDTieredSTOProxyFactory, {from: PolymathAccount}); }).then(() => { // H) Deploy the USDTieredSTOFactory (Use to generate the USDTieredSTOFactory contract which will used to collect the funds ). - return deployer.deploy(USDTieredSTOFactory, PolyToken, usdTieredSTOSetupCost, 0, 0, USDTieredSTOProxyFactory.address, {from: PolymathAccount}) + return deployer.deploy(USDTieredSTOFactory, PolyToken, usdTieredSTOSetupCost, 0, 0, USDTieredSTOLogic.address, {from: PolymathAccount}) }).then(() => { // I) Register the USDTieredSTOFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the USDTieredSTOFactory contract. @@ -327,7 +328,7 @@ module.exports = function (deployer, network, accounts) { CappedSTOFactory: ${CappedSTOFactory.address} USDTieredSTOFactory: ${USDTieredSTOFactory.address} - USDTieredSTOProxyFactory: ${USDTieredSTOProxyFactory.address} + USDTieredSTOLogic: ${USDTieredSTOLogic.address} CountTransferManagerFactory: ${CountTransferManagerFactory.address} PercentageTransferManagerFactory: ${PercentageTransferManagerFactory.address} diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index 81ee9606b..77c7a5e5d 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -17,7 +17,7 @@ const TrackedRedemptionFactory = artifacts.require("./TrackedRedemptionFactory.s const PercentageTransferManagerFactory = artifacts.require("./PercentageTransferManagerFactory.sol"); const ScheduledCheckpointFactory = artifacts.require('./ScheduledCheckpointFactory.sol'); const USDTieredSTOFactory = artifacts.require("./USDTieredSTOFactory.sol"); -const USDTieredSTOProxyFactory = artifacts.require("./USDTieredSTOProxyFactory"); +const USDTieredSTO = artifacts.require("./USDTieredSTO"); const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager"); const FeatureRegistry = artifacts.require("./FeatureRegistry.sol"); const STFactory = artifacts.require("./STFactory.sol"); @@ -65,6 +65,7 @@ let I_SecurityToken; let I_DummySTOFactory; let I_PolyToken; let I_STFactory; +let I_USDTieredSTOLogic; let I_PolymathRegistry; let I_SecurityTokenRegistryProxy; let I_STRProxied; @@ -343,9 +344,9 @@ export async function deployPresaleSTOAndVerified(accountPolymath, MRProxyInstan } export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_USDTieredSTOProxyFactory = await USDTieredSTOProxyFactory.new({from: accountPolymath}); + I_USDTieredSTOLogic = await USDTieredSTO.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); - I_USDTieredSTOFactory = await USDTieredSTOFactory.new(polyToken, setupCost, 0, 0, I_USDTieredSTOProxyFactory.address, { from: accountPolymath }); + I_USDTieredSTOFactory = await USDTieredSTOFactory.new(polyToken, setupCost, 0, 0, I_USDTieredSTOLogic.address, { from: accountPolymath }); assert.notEqual( I_USDTieredSTOFactory.address.valueOf(), diff --git a/test/p_usd_tiered_sto.js b/test/p_usd_tiered_sto.js index b59841e4e..ed2bf5651 100644 --- a/test/p_usd_tiered_sto.js +++ b/test/p_usd_tiered_sto.js @@ -4569,7 +4569,7 @@ contract("USDTieredSTO", accounts => { "Wrong Module added"); assert.equal(await I_USDTieredSTOFactory.title.call(), "USD Tiered STO", "Wrong Module added"); assert.equal(await I_USDTieredSTOFactory.getInstructions.call(), "Initialises a USD tiered STO.", "Wrong Module added"); - assert.equal(await I_USDTieredSTOFactory.version.call(), "1.0.0"); + assert.equal(await I_USDTieredSTOFactory.version.call(), "2.1.0"); let tags = await I_USDTieredSTOFactory.getTags.call(); assert.equal(web3.utils.hexToString(tags[0]), "USD"); assert.equal(web3.utils.hexToString(tags[1]), "Tiered");