diff --git a/CHANGELOG.md b/CHANGELOG.md index 94a8c2ce4..9343860b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,11 +35,14 @@ All notable changes to this project will be documented in this file. [__2.1.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __13-09-18__ -## CappedSTO 2.0.1 + +## CappedSTO 2.1.0 * `rate` is now accepted as multiplied by 10^18 to allow settting higher price than 1ETH/POLY per token. * Indivisble tokens are now supported. When trying to buy partial tokens, allowed full units of tokens will be purchased and remaining funds will be returned. ## USDTieredSTO 2.1.0 +* Added `stableCoinsRaised` function that returns amount of individual stable coin raised when address of that stable coin is passed. +* Added support for multiple stable coins in USDTSTO. * Added `buyTokensView` and `getTokensMintedByTier` to USDTSTO. * Added `getSTODetails` to USDTSTO. * Added an Array of Tiers that will hold data about every tier in USDTSTO. @@ -48,27 +51,68 @@ All notable changes to this project will be documented in this file. * 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` +* Added `getAccreditedData` to return accredited & non-accredited investor data. +* Event `TokenPurchase` has uint256 tier instead of uint8 tier. +* Event `SetAddresses` has non-indexed array of address of `_usdTokens` rather than single indexed address. +* Added `getUsdTokens()` function that returns array of accepted stable coin (usd token) addresses. +* Pass an array of `_usdToken` address in `configure` function instead of singleton address. This will require changes in bytes data generation when deploying a usdtsto through factory. ## GeneralTransferManager * `getInvestors`, `getAllInvestorsData`, `getInvestorsData` added to GTM to allow easy data queries. -* `modifyDefaults(uint64 _defaultFromTime, uint64 _defaultToTime)` added which sets a default timestamp used when `fromTime` or `toTime` are 0 +* `changeDefaults(uint64 _defaultFromTime, uint64 _defaultToTime)` added which sets a default timestamp used when `fromTime` or `toTime` are 0. * Add `address[] public investors` to record a list of all addresses that have been added to the whitelist (`getInvestors`). -* General Transfer Manager: Fix for when `allowAllWhitelistIssuances` is FALSE -* General Transfer Manager: Make GTM a Proxy based implementation to reduce deployment gas costs +* Fix for when `allowAllWhitelistIssuances` is FALSE +* Make GTM a Proxy based implementation to reduce deployment gas costs * Changed the version of `GeneralTransferManagerFactory` from `1.0.0` to `2.1.0`. +* `_investor` and `_addedBy` is now indexed in the `ModifyWhitelist` event. +* Add public variable `defaults` to get the offset timing. ## Manual Approval TransferManager * Removed `0x0` check for the `_from` address to `ManualApprovalTransferManager`. This allows for the Issuer/Transfer Agent to approve a one-off mint of tokens that otherwise would not be possible. * Changed the version of `ManualApprovalTransferManagerFactory` from `1.0.0` to `2.1.0`. * Deployed 2.0.1 `ManualApprovalTransferManagerFactory` to address 0x6af2afad53cb334e62b90ddbdcf3a086f654c298 +* Add `getActiveApprovalsToUser()` function to access all the active approvals for a user whether user is in the `from` or in `to`. +* Add `getApprovalDetails()` to get the details of the approval corresponds to `_from` and `_to` address. +* Add feature to modify the details of the active approval using `modifyApproval()` & `modifyApprovalMulti()`. +* Add `addManualApprovalMulti()` and `revokeManualApprovalMulti()` batch function for adding and revoking the manual approval respectively. +* Add `_description` parameter during the `addManualApproval()` function call. It will be a `bytes32` variable which depicts the cause of manual approval. +* Remove `addManualBlocking()` , `revokeManualBlocking()` functions. +* Add `getTotalApprovalsLength()` to get the number of active approvals. +* Add `getAllApprovals()` to get the details of all approvals. ## Dividends * Changed the version of `ERC20DividendCheckpointFactory` & `EtherDividendCheckpointFactory` from `1.0.0` to `2.1.0`. -* Applied proxy pattern to Dividends modules +* Applied proxy pattern to Dividends modules. +* During the launch of dividend module issuer need to pass the reclaimed wallet that receive the left over funds from the module. +i.e pass `_wallet` in `configure()` function of dividend module. It emits `SetWallet` event for the confirmation of the same. +* Add `changeWallet()` function to change the reclaimed wallet address (only be called by the owner). +* Add `getDividendsData()` getter to receive the details about all the dividend. +* Add `getDividendData()` getter to receive the details about the particular dividend by passing a corresponding dividend index. +* Add `getDividendProgress()` getter to retrieves the list of investors and their details corresponds to particular dividend. +* Add `getCheckpointData()` use to retrieves list of investors, their balances, and their current withholding tax percentage corresponds to checkpointId. +* `isExcluded()` a view function added to check whether an address is excluded from claming a dividend or not. +* `isClaimed()` a view function added to checks whether an address has claimed a dividend or not. +* DividendIndex is indexed in the events `ERC20DividendClaimed`, `ERC20DividendReclaimed`, `ERC20DividendWithholdingWithdrawn`. Similarly for the Ether dividend module `EtherDividendClaimed`, `EtherDividendReclaimed`, `EtherDividendClaimFailed`, `EtherDividendWithholdingWithdrawn`. +* `EXCLUDED_ADDRESS_LIMIT` changed from 50 to 150. + +## Experimental modules +* Remove the `SingleTradeVolumeRestrictionTMFactory.sol` and its corresponding module `SingleTradeVolumeRestrictionTM.sol`. +* Add the new TM called `BlacklistTransferManager.sol` and its corresponding factory `BlacklistTransferManagerFactory.sol`. +* Chnage the name of module from `LockupVolumeRestrictionTM.sol` to `LockUpTransferManager.sol`, similarly factory become `LockUpTransferManagerFactory.sol`. +* Add new module called `VestingEscrowWallet.sol` and its corresponding factory `VestingEscrowWalletFactory.sol`. + +## STR & MR +* `getArrayAddress(), getArrayBytes32(), getArrayUint()` are now public getters. +* `getUintValues(), getBoolValues(), getStringValues(), getAddressValues(), getBytes32Values(), getBytesValues()` rename to `getUintValue(), getBoolValue(), getStringValue(), getAddressValue(), getBytes32Value(), getBytesValue()`. #488 + +## Added +* Add new module called `VolumeRestrictionTM.sol` under the TransferManager modules list. It will be used to restrict the token +volume traded in a given rolling period. ## Changed * `getAllModulesAndPermsFromTypes()` does not take securityToken address as a parameter anymore. + # v1.5.0 - Release Candidate [__1.5.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __15-08-18__ @@ -107,7 +151,7 @@ All notable changes to this project will be documented in this file. * 0x0 and duplicate address in exclusions are no longer allowed in dividend modules. * All permissions are denied if no permission manager is active. * Generalize the STO varaible names and added them in `ISTO.sol` to use the common standard in all STOs. -* Generalize the event when any new token get registered with the polymath ecosystem. `LogNewSecurityToken` should emit _ticker, _name, _securityTokenAddress, _owner, _addedAt, _registrant respectively. #230 +* Generalize the event when any new token get registered with the polymath ecosystem. `LogNewSecurityToken` should emit _ticker_, _name_, _securityTokenAddress_, _owner_, _addedAt_, _registrant_ respectively. #230 * Change the function name of `withdraPoly` to `withdrawERC20` and make the function generalize to extract tokens from the ST contract. parmeters are contract address and the value need to extract from the securityToken. ## Removed @@ -339,7 +383,7 @@ allowed) * __buyTokensWithPoly__ has only one argument called `_investedPoly` only. Beneficiary Address should be its msg.sender. * __getRaiseEther()__ function name changed to __getRaisedEther()__. * __getRaisePoly()__ function name changed to __getRaisedPoly()__. -* `LogModuleAdded` emit one more variable called ___budget__. +* `LogModuleAdded` emit one more variable called __budget__. * `modules` mapping in the securityToken contract now returns __the array of ModuleData__. ## Removed @@ -351,7 +395,7 @@ allowed) ## Added * ModuleRegistry contract will provide the list of modules by there types. -* `SecurityTokenRegistry` is now working on the basis of the proxy version of the securitytoken contract. For that SecurityTokenRegistry has one more variable in the constructor called _STVersionProxy . +* `SecurityTokenRegistry` is now working on the basis of the proxy version of the securitytoken contract. For that SecurityTokenRegistry has one more variable in the constructor called _STVersionProxy_ . * `setProtocolVersion` new function added in the SecurityTokenRegistry to set the protocol version followed to generate the securityToken. Only be called by the `polymath admin`. * `SecurityToken` now have the integration with polyToken. At the time of `addModule()` SecurityToken approve the cost of the module to moduleFactory as the spender. * New function `withdrawPoly(uint256 _amount)` is added to withdrawal the unused POLY from the securityToken contract. Called only by the owner of the contract. @@ -378,7 +422,7 @@ allowed) * Deployment of the securityToken is now performed by the proxy contracts and call being generated form the SecurityTokenRegistry. * `TickerRegistrar` renamed as `TickerRegistry`. * TickerRegistry is now Ownable contract. -* `setTokenRegistrar` functio of TickerRegistry renamed to `setTokenRegistry`. +* `setTokenRegistrar` function of TickerRegistry renamed to `setTokenRegistry`. * SecurityToken constructor has one change in the variable. i.e `_moduleRegistry` contract address is replaced by the `_owner` address. * Their is no `_perm` parameter in the `addModule()` function of the securityToken contract. Now only 4 parameters left. * Type of Mudules changed diff --git a/contracts/STRGetter.sol b/contracts/STRGetter.sol index 29d5264fd..85c2988bb 100644 --- a/contracts/STRGetter.sol +++ b/contracts/STRGetter.sol @@ -5,6 +5,8 @@ import "./libraries/Util.sol"; import "./libraries/Encoder.sol"; import "./interfaces/IOwnable.sol"; import "./libraries/VersionUtils.sol"; +import "./interfaces/ISecurityToken.sol"; +import "./modules/PermissionManager/IPermissionManager.sol"; contract STRGetter is EternalStorage { @@ -17,28 +19,33 @@ contract STRGetter is EternalStorage { * @param _owner is the address which owns the list of tickers */ function getTickersByOwner(address _owner) external view returns(bytes32[] memory) { - uint counter = 0; + uint256 count = 0; // accessing the data structure userTotickers[_owner].length bytes32[] memory tickers = getArrayBytes32(Encoder.getKey("userToTickers", _owner)); uint i; for (i = 0; i < tickers.length; i++) { - string memory ticker = Util.bytes32ToString(tickers[i]); - /*solium-disable-next-line security/no-block-members*/ - if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || getTickerStatus(ticker)) { - counter ++; + if (_ownerInTicker(tickers[i])) { + count++; } } - bytes32[] memory tempList = new bytes32[](counter); - counter = 0; + bytes32[] memory result = new bytes32[](count); + count = 0; for (i = 0; i < tickers.length; i++) { - string memory ticker = Util.bytes32ToString(tickers[i]); - /*solium-disable-next-line security/no-block-members*/ - if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || getTickerStatus(ticker)) { - tempList[counter] = tickers[i]; - counter ++; + if (_ownerInTicker(tickers[i])) { + result[count] = tickers[i]; + count++; } } - return tempList; + return result; + } + + function _ownerInTicker(bytes32 _ticker) internal view returns (bool) { + string memory ticker = Util.bytes32ToString(_ticker); + /*solium-disable-next-line security/no-block-members*/ + if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || getBoolValue(Encoder.getKey("registeredTickers_status", ticker))) { + return true; + } + return false; } /** @@ -54,7 +61,7 @@ contract STRGetter is EternalStorage { * @notice Returns the list of all tokens * @dev Intention is that this is called off-chain so block gas limit is not relevant */ - function getTokens() external view returns(address[] memory) { + function getTokens() public view returns(address[] memory) { return _getTokens(true, address(0)); } /** @@ -67,38 +74,86 @@ contract STRGetter is EternalStorage { // This ensures we find tokens, even if their owner has been modified address[] memory activeUsers = getArrayAddress(Encoder.getKey("activeUsers")); bytes32[] memory tickers; - address token; uint256 count = 0; uint256 i = 0; uint256 j = 0; for (i = 0; i < activeUsers.length; i++) { tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); for (j = 0; j < tickers.length; j++) { - token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); - if (token != address(0)) { - if (_allTokens || IOwnable(token).owner() == _owner) { - count = count + 1; - } + if (address(0) != _ownerInToken(tickers[j], _allTokens, _owner)) { + count++; } } } - uint256 index = 0; address[] memory result = new address[](count); + count = 0; + address token; for (i = 0; i < activeUsers.length; i++) { tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); for (j = 0; j < tickers.length; j++) { - token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); - if (token != address(0)) { - if (_allTokens || IOwnable(token).owner() == _owner) { - result[index] = token; - index = index + 1; - } + token = _ownerInToken(tickers[j], _allTokens, _owner); + if (address(0) != token) { + result[count] = token; + count++; } } } return result; } + function _ownerInToken(bytes32 _ticker, bool _allTokens, address _owner) internal view returns(address) { + address token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(_ticker))); + if (token != address(0)) { + if (_allTokens || IOwnable(token).owner() == _owner) { + return token; + } + } + return address(0); + } + + /** + * @notice Returns the list of tokens to which the delegate has some access + * @param _delegate is the address for the delegate + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokensByDelegate(address _delegate) external view returns(address[] memory) { + // Loop over all active users, then all associated tickers of those users + // This ensures we find tokens, even if their owner has been modified + address[] memory tokens = getTokens(); + uint256 count = 0; + uint256 i = 0; + for (i = 0; i < tokens.length; i++) { + if (_delegateInToken(tokens[i], _delegate)) { + count++; + } + } + address[] memory result = new address[](count); + count = 0; + for (i = 0; i < tokens.length; i++) { + if (_delegateInToken(tokens[i], _delegate)) { + result[count] = tokens[i]; + count++; + } + } + return result; + } + + function _delegateInToken(address _token, address _delegate) internal view returns(bool) { + uint256 j = 0; + address[] memory permissionManagers; + bool isArchived; + permissionManagers = ISecurityToken(_token).getModulesByType(1); + for (j = 0; j < permissionManagers.length; j++) { + (,,, isArchived,,) = ISecurityToken(_token).getModule(permissionManagers[j]); + if (!isArchived) { + if (IPermissionManager(permissionManagers[j]).checkDelegate(_delegate)) { + return true; + } + } + } + return false; + } + /** * @notice Returns the owner and timestamp for a given ticker * @param _ticker is the ticker symbol diff --git a/contracts/SecurityTokenRegistry.sol b/contracts/SecurityTokenRegistry.sol index 88c9d31d5..6fc90ef33 100644 --- a/contracts/SecurityTokenRegistry.sol +++ b/contracts/SecurityTokenRegistry.sol @@ -6,13 +6,13 @@ import "./interfaces/IOwnable.sol"; import "./interfaces/ISTFactory.sol"; import "./interfaces/ISecurityTokenRegistry.sol"; import "./interfaces/IPolymathRegistry.sol"; +import "./interfaces/IOracle.sol"; import "./storage/EternalStorage.sol"; import "./libraries/Util.sol"; import "./libraries/Encoder.sol"; import "./libraries/VersionUtils.sol"; -import "./proxy/Proxy.sol"; -import "./interfaces/IOracle.sol"; import "./libraries/DecimalMath.sol"; +import "./proxy/Proxy.sol"; /** * @title Registry contract for issuers to register their tickers and security tokens @@ -126,7 +126,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { - require(msg.sender == owner(), "sender must be owner"); + require(msg.sender == owner(), "Only owner"); _; } @@ -136,7 +136,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { modifier whenNotPausedOrOwner() { if (msg.sender == owner()) _; else { - require(!isPaused(), "Already paused"); + require(!isPaused(), "Paused"); _; } } @@ -145,7 +145,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Modifier to make a function callable only when the contract is not paused and ignore is msg.sender is owner. */ modifier whenNotPaused() { - require(!isPaused(), "Already paused"); + require(!isPaused(), "Paused"); _; } @@ -153,7 +153,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @notice Modifier to make a function callable only when the contract is paused. */ modifier whenPaused() { - require(isPaused(), "Should not be paused"); + require(isPaused(), "Not paused"); _; } @@ -181,12 +181,12 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { external payable { - require(!getBoolValue(INITIALIZE),"already initialized"); + require(!getBoolValue(INITIALIZE),"Initialized"); require( _STFactory != address(0) && _owner != address(0) && _polymathRegistry != address(0) && _getterContract != address(0), "Invalid address" ); - require(_stLaunchFee != 0 && _tickerRegFee != 0, "Fees should not be 0"); + require(_stLaunchFee != 0 && _tickerRegFee != 0, "Bad fee"); set(STLAUNCHFEE, _stLaunchFee); set(TICKERREGFEE, _tickerRegFee); set(EXPIRYLIMIT, uint256(60 * 1 days)); @@ -259,12 +259,12 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _tokenName is the name of the token */ function registerTicker(address _owner, string calldata _ticker, string calldata _tokenName) external whenNotPausedOrOwner { - require(_owner != address(0), "Owner should not be 0x"); - require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Ticker length range (0,10]"); + require(_owner != address(0), "Bad address"); + require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Bad ticker"); // Attempt to charge the reg fee if it is > 0 USD (uint256 _usdFee, uint256 _polyFee) = _takeFee(TICKERREGFEE); string memory ticker = Util.upper(_ticker); - require(_tickerAvailable(ticker), "Ticker is reserved"); + require(_tickerAvailable(ticker), "Ticker reserved"); // Check whether ticker was previously registered (and expired) address previousOwner = _tickerOwner(ticker); if (previousOwner != address(0)) { @@ -316,10 +316,10 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { external onlyOwner { - require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Ticker length range (0,10]"); - require(_expiryDate != 0 && _registrationDate != 0, "Dates should not be 0"); - require(_registrationDate <= _expiryDate, "Registration date should < expiry date"); - require(_owner != address(0), "Invalid address"); + require(bytes(_ticker).length > 0 && bytes(_ticker).length <= 10, "Bad ticker"); + require(_expiryDate != 0 && _registrationDate != 0, "Bad dates"); + require(_registrationDate <= _expiryDate, "Bad dates"); + require(_owner != address(0), "Bad address"); string memory ticker = Util.upper(_ticker); _modifyTicker(_owner, ticker, _tokenName, _registrationDate, _expiryDate, _status); } @@ -346,7 +346,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { } // If status is true, there must be a security token linked to the ticker already if (_status) { - require(getAddressValue(Encoder.getKey("tickerToSecurityToken", _ticker)) != address(0), "Token not registered"); + require(getAddressValue(Encoder.getKey("tickerToSecurityToken", _ticker)) != address(0), "Not registered"); } _addTicker(_owner, _ticker, _tokenName, _registrationDate, _expiryDate, _status, true, uint256(0), uint256(0)); } @@ -362,7 +362,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { function removeTicker(string calldata _ticker) external onlyOwner { string memory ticker = Util.upper(_ticker); address owner = _tickerOwner(ticker); - require(owner != address(0), "Ticker doesn't exist"); + require(owner != address(0), "Bad ticker"); _deleteTickerOwnership(owner, ticker); set(Encoder.getKey("tickerToSecurityToken", ticker), address(0)); _storeTickerDetails(ticker, address(0), 0, 0, "", false); @@ -438,12 +438,12 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { */ function transferTickerOwnership(address _newOwner, string calldata _ticker) external whenNotPausedOrOwner { string memory ticker = Util.upper(_ticker); - require(_newOwner != address(0), "Invalid address"); + require(_newOwner != address(0), "Bad address"); bytes32 ownerKey = Encoder.getKey("registeredTickers_owner", ticker); - require(getAddressValue(ownerKey) == msg.sender, "Not authorised"); + require(getAddressValue(ownerKey) == msg.sender, "Only owner"); if (_tickerStatus(ticker)) require( IOwnable(getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker))).owner() == _newOwner, - "New owner does not match token owner" + "Owner mismatch" ); _deleteTickerOwnership(msg.sender, ticker); _setTickerOwnership(_newOwner, ticker); @@ -472,7 +472,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _newExpiry is the new expiry for newly generated tickers */ function changeExpiryLimit(uint256 _newExpiry) external onlyOwner { - require(_newExpiry >= 1 days, "Expiry should >= 1 day"); + require(_newExpiry >= 1 days, "Bad dates"); emit ChangeExpiryLimit(getUintValue(EXPIRYLIMIT), _newExpiry); set(EXPIRYLIMIT, _newExpiry); } @@ -497,14 +497,14 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { external whenNotPausedOrOwner { - require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Ticker length > 0"); + require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad ticker"); string memory ticker = Util.upper(_ticker); bytes32 statusKey = Encoder.getKey("registeredTickers_status", ticker); require(!getBoolValue(statusKey), "Already deployed"); set(statusKey, true); require(_tickerOwner(ticker) == msg.sender, "Not authorised"); /*solium-disable-next-line security/no-block-members*/ - require(getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now, "Ticker gets expired"); + require(getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now, "Ticker expired"); (uint256 _usdFee, uint256 _polyFee) = _takeFee(STLAUNCHFEE); address newSecurityTokenAddress = ISTFactory(getAddressValue(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion"))))).deployToken( @@ -544,11 +544,11 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { external onlyOwner { - require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "String length > 0"); - require(bytes(_ticker).length <= 10, "Ticker length range (0,10]"); - require(_deployedAt != 0 && _owner != address(0), "0 value params not allowed"); + require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Bad data"); + require(bytes(_ticker).length <= 10, "Bad ticker"); + require(_deployedAt != 0 && _owner != address(0), "Bad data"); string memory ticker = Util.upper(_ticker); - require(_securityToken != address(0), "ST address is 0x"); + require(_securityToken != address(0), "Bad address"); uint256 registrationTime = getUintValue(Encoder.getKey("registeredTickers_registrationDate", ticker)); uint256 expiryTime = getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)); if (registrationTime == 0) { @@ -594,7 +594,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _newOwner The address to transfer ownership to. */ function transferOwnership(address _newOwner) external onlyOwner { - require(_newOwner != address(0), "Invalid address"); + require(_newOwner != address(0), "Bad address"); emit OwnershipTransferred(getAddressValue(OWNER), _newOwner); set(OWNER, _newOwner); } @@ -623,7 +623,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { */ function changeTickerRegistrationFee(uint256 _tickerRegFee) external onlyOwner { uint256 fee = getUintValue(TICKERREGFEE); - require(fee != _tickerRegFee, "Fee not changed"); + require(fee != _tickerRegFee, "Bad fee"); emit ChangeTickerRegistrationFee(fee, _tickerRegFee); set(TICKERREGFEE, _tickerRegFee); } @@ -634,7 +634,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { */ function changeSecurityLaunchFee(uint256 _stLaunchFee) external onlyOwner { uint256 fee = getUintValue(STLAUNCHFEE); - require(fee != _stLaunchFee, "Fee not changed"); + require(fee != _stLaunchFee, "Bad fee"); emit ChangeSecurityLaunchFee(fee, _stLaunchFee); set(STLAUNCHFEE, _stLaunchFee); } @@ -644,7 +644,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _tokenContract is the address of the token contract */ function reclaimERC20(address _tokenContract) external onlyOwner { - require(_tokenContract != address(0), "Invalid address"); + require(_tokenContract != address(0), "Bad address"); IERC20 token = IERC20(_tokenContract); uint256 balance = token.balanceOf(address(this)); require(token.transfer(owner(), balance), "Transfer failed"); @@ -660,7 +660,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _patch Patch version of the proxy */ function setProtocolVersion(address _STFactoryAddress, uint8 _major, uint8 _minor, uint8 _patch) external onlyOwner { - require(_STFactoryAddress != address(0), "0x address is not allowed"); + require(_STFactoryAddress != address(0), "Bad address"); _setProtocolVersion(_STFactoryAddress, _major, _minor, _patch); } @@ -675,7 +675,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { uint24 _packedVersion = VersionUtils.pack(_major, _minor, _patch); require( VersionUtils.isValidVersion(VersionUtils.unpack(uint24(getUintValue(Encoder.getKey("latestVersion")))), _version), - "In-valid version" + "Bad version" ); set(Encoder.getKey("latestVersion"), uint256(_packedVersion)); set(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion"))), _STFactoryAddress); @@ -686,7 +686,7 @@ contract SecurityTokenRegistry is EternalStorage, Proxy { * @param _newAddress is the address of the polytoken. */ function updatePolyTokenAddress(address _newAddress) external onlyOwner { - require(_newAddress != address(0), "Invalid address"); + require(_newAddress != address(0), "Bad address"); set(POLYTOKEN, _newAddress); } diff --git a/contracts/interfaces/ISecurityTokenRegistry.sol b/contracts/interfaces/ISecurityTokenRegistry.sol index 18e1e6951..13733beb9 100644 --- a/contracts/interfaces/ISecurityTokenRegistry.sol +++ b/contracts/interfaces/ISecurityTokenRegistry.sol @@ -29,7 +29,7 @@ interface ISecurityTokenRegistry { address _securityToken, string calldata _tokenDetails, uint256 _deployedAt - ) + ) external; /** @@ -140,7 +140,7 @@ interface ISecurityTokenRegistry { uint256 _registrationDate, uint256 _expiryDate, bool _status - ) + ) external; /** @@ -186,6 +186,13 @@ interface ISecurityTokenRegistry { */ function getTickerRegistrationFee() external view returns(uint256); + /** + * @notice Returns the list of tokens to which the delegate has some access + * @param _delegate is the address for the delegate + * @dev Intention is that this is called off-chain so block gas limit is not relevant + */ + function getTokensByDelegate(address _delegate) external view returns(address[] memory); + /** * @notice Gets the expiry limit * @return Expiry limit diff --git a/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol b/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol index 64301c681..94c730dfb 100644 --- a/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol +++ b/contracts/modules/Experimental/Mixed/ScheduledCheckpoint.sol @@ -3,6 +3,7 @@ pragma solidity ^0.5.0; import "./../../Checkpoint/ICheckpoint.sol"; import "../../TransferManager/TransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../../libraries/BokkyPooBahsDateTimeLibrary.sol"; /** * @title Burn module for burning tokens and keeping track of burnt amounts @@ -10,22 +11,26 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; contract ScheduledCheckpoint is ICheckpoint, TransferManager { using SafeMath for uint256; + enum TimeUnit {SECONDS, DAYS, WEEKS, MONTHS, YEARS} + struct Schedule { bytes32 name; uint256 startTime; uint256 nextTime; uint256 interval; + TimeUnit timeUnit; uint256 index; uint256[] checkpointIds; uint256[] timestamps; uint256[] periods; + uint256 totalPeriods; } bytes32[] public names; mapping(bytes32 => Schedule) public schedules; - event AddSchedule(bytes32 _name, uint256 _startTime, uint256 _interval); + event AddSchedule(bytes32 _name, uint256 _startTime, uint256 _interval, TimeUnit _timeUint); event RemoveSchedule(bytes32 _name); /** @@ -48,17 +53,19 @@ contract ScheduledCheckpoint is ICheckpoint, TransferManager { * @param _name name of the new schedule (must be unused) * @param _startTime start time of the schedule (first checkpoint) * @param _interval interval at which checkpoints should be created + * @param _timeUnit unit of time at which checkpoints should be created */ - function addSchedule(bytes32 _name, uint256 _startTime, uint256 _interval) external onlyOwner { + function addSchedule(bytes32 _name, uint256 _startTime, uint256 _interval, TimeUnit _timeUnit) external onlyOwner { require(_startTime > now, "Start time must be in the future"); require(schedules[_name].name == bytes32(0), "Name already in use"); schedules[_name].name = _name; schedules[_name].startTime = _startTime; schedules[_name].nextTime = _startTime; schedules[_name].interval = _interval; + schedules[_name].timeUnit = _timeUnit; schedules[_name].index = names.length; names.push(_name); - emit AddSchedule(_name, _startTime, _interval); + emit AddSchedule(_name, _startTime, _interval, _timeUnit); } /** @@ -86,10 +93,10 @@ contract ScheduledCheckpoint is ICheckpoint, TransferManager { address, /* _to */ uint256, /* _amount */ bytes calldata /* _data */ - ) + ) external onlySecurityToken - returns(Result) + returns(Result) { if (!paused) { _updateAll(); @@ -106,10 +113,10 @@ contract ScheduledCheckpoint is ICheckpoint, TransferManager { address, /* _to */ uint256, /* _amount */ bytes memory /* _data */ - ) + ) public - view - returns(Result, bytes32) + view + returns(Result, bytes32) { return (Result.NA, bytes32(0)); } @@ -123,11 +130,14 @@ contract ScheduledCheckpoint is ICheckpoint, TransferManager { uint256, uint256, uint256, + TimeUnit, + uint256[] memory, uint256[] memory, uint256[] memory, - uint256[] memory + uint256 ){ - return (schedules[_name].name, schedules[_name].startTime, schedules[_name].nextTime, schedules[_name].interval, schedules[_name].checkpointIds, schedules[_name].timestamps, schedules[_name].periods); + Schedule storage schedule = schedules[_name]; + return (schedule.name, schedule.startTime, schedule.nextTime, schedule.interval, schedule.timeUnit, schedule.checkpointIds, schedule.timestamps, schedule.periods, schedule.totalPeriods); } /** @@ -142,10 +152,27 @@ contract ScheduledCheckpoint is ICheckpoint, TransferManager { Schedule storage schedule = schedules[_name]; if (schedule.nextTime <= now) { uint256 checkpointId = ISecurityToken(securityToken).createCheckpoint(); - uint256 periods = now.sub(schedule.nextTime).div(schedule.interval).add(1); - schedule.timestamps.push(schedule.nextTime); - schedule.nextTime = periods.mul(schedule.interval).add(schedule.nextTime); schedule.checkpointIds.push(checkpointId); + schedule.timestamps.push(schedule.nextTime); + uint256 periods; + if (schedule.timeUnit == TimeUnit.SECONDS ) { + periods = now.sub(schedule.nextTime).div(schedule.interval).add(1); + schedule.nextTime = periods.mul(schedule.interval).add(schedule.nextTime); + } else if (schedule.timeUnit == TimeUnit.DAYS ) { + periods = BokkyPooBahsDateTimeLibrary.diffDays(schedule.nextTime, now).div(schedule.interval).add(1); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addDays(schedule.nextTime, periods.mul(schedule.interval)); + } else if (schedule.timeUnit == TimeUnit.WEEKS ) { + periods = BokkyPooBahsDateTimeLibrary.diffDays(schedule.nextTime, now).div(7).div(schedule.interval).add(1); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addDays(schedule.nextTime, periods.mul(schedule.interval).mul(7)); + } else if (schedule.timeUnit == TimeUnit.MONTHS ) { + periods = BokkyPooBahsDateTimeLibrary.diffMonths(schedule.nextTime, now).div(schedule.interval).add(1); + uint256 totalPeriods = schedule.totalPeriods.add(periods); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addMonths(schedule.startTime, totalPeriods.mul(schedule.interval)); + } else if (schedule.timeUnit == TimeUnit.YEARS ) { + periods = BokkyPooBahsDateTimeLibrary.diffYears(schedule.nextTime, now).div(schedule.interval).add(1); + schedule.nextTime = BokkyPooBahsDateTimeLibrary.addYears(schedule.nextTime, periods.mul(schedule.interval)); + } + schedule.totalPeriods = schedule.totalPeriods.add(periods); schedule.periods.push(periods); } } @@ -169,7 +196,7 @@ contract ScheduledCheckpoint is ICheckpoint, TransferManager { */ function getTokensByPartition(address /*_owner*/, bytes32 /*_partition*/) external view returns(uint256){ return 0; - } + } /** * @notice Return the permissions flag that are associated with CountTransferManager diff --git a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol index 5519e44b6..297ba41bf 100644 --- a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol +++ b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol @@ -89,7 +89,7 @@ contract LockUpTransferManager is TransferManager { address /* _to*/, uint256 _amount, bytes memory /* _data */ - ) + ) public view returns(Result, bytes32) @@ -118,7 +118,7 @@ contract LockUpTransferManager is TransferManager { uint256 _releaseFrequencySeconds, bytes32 _lockupName ) - public + external withPerm(ADMIN) { _addNewLockUpType( @@ -175,7 +175,7 @@ contract LockUpTransferManager is TransferManager { address _userAddress, bytes32 _lockupName ) - public + external withPerm(ADMIN) { _addLockUpByName(_userAddress, _lockupName); @@ -193,7 +193,7 @@ contract LockUpTransferManager is TransferManager { external withPerm(ADMIN) { - require(_userAddresses.length == _lockupNames.length, "Length mismatch"); + _checkLengthOfArray(_userAddresses.length, _lockupNames.length); for (uint256 i = 0; i < _userAddresses.length; i++) { _addLockUpByName(_userAddresses[i], _lockupNames[i]); } @@ -219,7 +219,7 @@ contract LockUpTransferManager is TransferManager { external withPerm(ADMIN) { - _addNewLockUpToUser( + _addNewLockUpToUser( _userAddress, _lockupAmount, _startTime, @@ -295,7 +295,7 @@ contract LockUpTransferManager is TransferManager { * @param _lockupNames Array of the names of the lockup that needs to be removed. */ function removeLockUpFromUserMulti(address[] calldata _userAddresses, bytes32[] calldata _lockupNames) external withPerm(ADMIN) { - require(_userAddresses.length == _lockupNames.length, "Length mismatch"); + _checkLengthOfArray(_userAddresses.length, _lockupNames.length); for (uint256 i = 0; i < _userAddresses.length; i++) { _removeLockUpFromUser(_userAddresses[i], _lockupNames[i]); } @@ -368,7 +368,7 @@ contract LockUpTransferManager is TransferManager { * @notice Get a specific element in a user's lockups array given the user's address and the element index * @param _lockupName The name of the lockup */ - function getLockUp(bytes32 _lockupName) external view returns ( + function getLockUp(bytes32 _lockupName) public view returns ( uint256 lockupAmount, uint256 startTime, uint256 lockUpPeriodSeconds, @@ -387,13 +387,38 @@ contract LockUpTransferManager is TransferManager { return (uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)); } + /** + * @notice Return the data of the lockups + */ + function getAllLockupData() external view returns( + bytes32[] memory lockupNames, + uint256[] memory lockupAmounts, + uint256[] memory startTimes, + uint256[] memory lockUpPeriodSeconds, + uint256[] memory releaseFrequencySeconds, + uint256[] memory unlockedAmounts + ) + { + uint256 length = lockupArray.length; + lockupAmounts = new uint256[](length); + startTimes = new uint256[](length); + lockUpPeriodSeconds = new uint256[](length); + releaseFrequencySeconds = new uint256[](length); + unlockedAmounts = new uint256[](length); + lockupNames = new bytes32[](length); + for (uint256 i = 0; i < length; i++) { + (lockupAmounts[i], startTimes[i], lockUpPeriodSeconds[i], releaseFrequencySeconds[i], unlockedAmounts[i]) = getLockUp(lockupArray[i]); + lockupNames[i] = lockupArray[i]; + } + } + /** * @notice get the list of the users of a lockup type * @param _lockupName Name of the lockup type - * @return address List of users associated with the blacklist + * @return address List of users associated with the given lockup name */ function getListOfAddresses(bytes32 _lockupName) external view returns(address[] memory) { - require(lockups[_lockupName].startTime != 0, "Invalid blacklist"); + _validLockUpCheck(_lockupName); return lockupToUsers[_lockupName]; } @@ -420,10 +445,9 @@ contract LockUpTransferManager is TransferManager { * @return uint256 Total locked tokens amount */ function getLockedTokenToUser(address _userAddress) public view returns(uint256) { - require(_userAddress != address(0), "Invalid address"); + _checkZeroAddress(_userAddress); bytes32[] memory userLockupNames = userToLockups[_userAddress]; uint256 totalRemainingLockedAmount = 0; - for (uint256 i = 0; i < userLockupNames.length; i++) { // Find out the remaining locked amount for a given lockup uint256 remainingLockedAmount = lockups[userLockupNames[i]].lockupAmount.sub(_getUnlockedAmountForLockup(userLockupNames[i])); @@ -470,8 +494,8 @@ contract LockUpTransferManager is TransferManager { } function _removeLockupType(bytes32 _lockupName) internal { - require(lockups[_lockupName].startTime != 0, "Invalid lockup"); - require(lockupToUsers[_lockupName].length == 0, "Not empty"); + _validLockUpCheck(_lockupName); + require(lockupToUsers[_lockupName].length == 0); // delete lockup type delete(lockups[_lockupName]); uint256 i = 0; @@ -497,14 +521,11 @@ contract LockUpTransferManager is TransferManager { internal { /*solium-disable-next-line security/no-block-members*/ - uint256 startTime = _startTime; - if (_startTime == 0) { - startTime = now; + _startTime = now; } - require(startTime >= now, "Invalid start time"); - require(lockups[_lockupName].lockupAmount != 0, "Doesn't exist"); - + _checkValidStartTime(_startTime); + _validLockUpCheck(_lockupName); _checkLockUpParams( _lockupAmount, _lockUpPeriodSeconds, @@ -513,14 +534,14 @@ contract LockUpTransferManager is TransferManager { lockups[_lockupName] = LockUp( _lockupAmount, - startTime, + _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds ); emit ModifyLockUpType( _lockupAmount, - startTime, + _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds, _lockupName @@ -528,11 +549,10 @@ contract LockUpTransferManager is TransferManager { } function _removeLockUpFromUser(address _userAddress, bytes32 _lockupName) internal { - require(_userAddress != address(0), "Invalid address"); - require(_lockupName != bytes32(0), "Invalid lockup name"); + _checkZeroAddress(_userAddress); + _checkValidName(_lockupName); require( - userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, - "Not empty" + userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName ); // delete the user from the lockup type @@ -568,7 +588,7 @@ contract LockUpTransferManager is TransferManager { ) internal { - require(_userAddress != address(0), "Invalid address"); + _checkZeroAddress(_userAddress); _addNewLockUpType( _lockupAmount, _startTime, @@ -585,8 +605,8 @@ contract LockUpTransferManager is TransferManager { ) internal { - require(_userAddress != address(0), "Invalid address"); - require(lockups[_lockupName].startTime >= now, "Lockup expired"); + _checkZeroAddress(_userAddress); + _checkValidStartTime(lockups[_lockupName].startTime); userToLockupIndex[_userAddress][_lockupName] = userToLockups[_userAddress].length; lockupToUserIndex[_lockupName][_userAddress] = lockupToUsers[_lockupName].length; @@ -604,18 +624,17 @@ contract LockUpTransferManager is TransferManager { ) internal { - uint256 startTime = _startTime; - require(_lockupName != bytes32(0), "Invalid name"); - require(lockups[_lockupName].lockupAmount == 0, "Already exist"); /*solium-disable-next-line security/no-block-members*/ if (_startTime == 0) { - startTime = now; + _startTime = now; } - require(startTime >= now, "Invalid start time"); + _checkValidName(_lockupName); + require(lockups[_lockupName].lockupAmount == 0, "Already exist"); + _checkValidStartTime(_startTime); _checkLockUpParams(_lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); - lockups[_lockupName] = LockUp(_lockupAmount, startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + lockups[_lockupName] = LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); lockupArray.push(_lockupName); - emit AddNewLockUpType(_lockupName, _lockupAmount, startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + emit AddNewLockUpType(_lockupName, _lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); } /** @@ -641,6 +660,26 @@ contract LockUpTransferManager is TransferManager { ); } + function _checkValidStartTime(uint256 _startTime) internal view { + require(_startTime >= now, "Invalid startTime or expired"); + } + + function _checkZeroAddress(address _userAddress) internal pure { + require(_userAddress != address(0), "Invalid address"); + } + + function _validLockUpCheck(bytes32 _lockupName) internal view { + require(lockups[_lockupName].startTime != 0, "Doesn't exist"); + } + + function _checkValidName(bytes32 _name) internal pure { + require(_name != bytes32(0), "Invalid name"); + } + + function _checkLengthOfArray(uint256 _length1, uint256 _length2) internal pure { + require(_length1 == _length2, "Length mismatch"); + } + /** * @notice return the amount of tokens for a given user as per the partition * @param _owner Whom token amount need to query @@ -657,7 +696,7 @@ contract LockUpTransferManager is TransferManager { return _currentBalance.sub(getLockedTokenToUser(_owner)); } return 0; - } + } /** * @notice This function returns the signature of configure function diff --git a/test/g_general_permission_manager.js b/test/g_general_permission_manager.js index 6e836fb33..e513972ff 100644 --- a/test/g_general_permission_manager.js +++ b/test/g_general_permission_manager.js @@ -6,6 +6,7 @@ import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployGPMAndVerifyed } from "./helpers/createInstances"; const SecurityToken = artifacts.require("./SecurityToken.sol"); +const SecurityTokenRegistryInterface = artifacts.require("./ISecurityTokenRegistry.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); const STGetter = artifacts.require("./STGetter"); @@ -52,6 +53,7 @@ contract("GeneralPermissionManager", async (accounts) => { let I_PolymathRegistry; let I_STRGetter; let I_STGetter; + let I_SecurityTokenRegistryInterface; let stGetter; // SecurityToken Details @@ -111,7 +113,7 @@ contract("GeneralPermissionManager", async (accounts) => { [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, 0); // STEP 6: Deploy the GeneralDelegateManagerFactory [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, new BN(web3.utils.toWei("500"))); - + I_SecurityTokenRegistryInterface = await SecurityTokenRegistryInterface.at(I_SecurityTokenRegistryProxy.address); // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: --------------------- @@ -304,13 +306,66 @@ contract("GeneralPermissionManager", async (accounts) => { }); it("Should return all delegates", async () => { + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 0); await I_GeneralPermissionManager.addDelegate(account_delegate2, delegateDetails, { from: token_owner }); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 1); let tx = await I_GeneralPermissionManager.getAllDelegates.call(); assert.equal(tx.length, 2); assert.equal(tx[0], account_delegate); assert.equal(tx[1], account_delegate2); }); + it("Should create a new token and add some more delegates, then get them", async() => { + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx1 = await I_STRProxied.registerTicker(token_owner, "DEL", contact, { from: token_owner }); + assert.equal(tx1.logs[0].args._owner, token_owner); + assert.equal(tx1.logs[0].args._ticker, "DEL"); + + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx2 = await I_STRProxied.generateSecurityToken(name, "DEL", tokenDetails, false, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx2.logs[2].args._ticker, "DEL", "SecurityToken doesn't get deployed"); + + let I_SecurityToken_DEL = await SecurityToken.at(tx2.logs[2].args._securityTokenAddress); + + const tx = await I_SecurityToken_DEL.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" + ); + + let I_GeneralPermissionManager_DEL = await GeneralPermissionManager.at(tx.logs[2].args._module); + await I_GeneralPermissionManager_DEL.addDelegate(account_delegate3, delegateDetails, { from: token_owner}); + + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3))[0], I_SecurityToken_DEL.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3)).length, 1); + await I_GeneralPermissionManager_DEL.addDelegate(account_delegate2, delegateDetails, { from: token_owner}); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[0], I_SecurityToken.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2))[1], I_SecurityToken_DEL.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3))[0], I_SecurityToken_DEL.address); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate)).length, 1); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate2)).length, 2); + assert.equal((await I_SecurityTokenRegistryInterface.getTokensByDelegate.call(account_delegate3)).length, 1); + let tx4 = await I_GeneralPermissionManager_DEL.getAllDelegates.call(); + assert.equal(tx4.length, 2); + assert.equal(tx4[0], account_delegate3, account_delegate2); + }); + it("Should check is delegate for 0x address - failed 0x address is not allowed", async () => { await catchRevert(I_GeneralPermissionManager.checkDelegate.call(address_zero)); }); diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js index d7bc6b76f..5cde44801 100644 --- a/test/w_lockup_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -956,6 +956,10 @@ contract('LockUpTransferManager', accounts => { I_LockUpTransferManager.removeLockupType(web3.utils.fromAscii("l_lockup"), {from: token_owner}) ); }) + + it("Should get the data of all lockups", async() => { + console.log(await I_LockUpTransferManager.getAllLockupData.call()); + }); it("Should succesfully get the non existed lockup value, it will give everything 0", async() => { let data = await I_LockUpTransferManager.getLockUp(web3.utils.fromAscii("foo")); diff --git a/test/x_scheduled_checkpoints.js b/test/x_scheduled_checkpoints.js index 6f0818b35..3f6f54181 100644 --- a/test/x_scheduled_checkpoints.js +++ b/test/x_scheduled_checkpoints.js @@ -14,6 +14,13 @@ let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port contract("ScheduledCheckpoint", async (accounts) => { + + const SECONDS = 0; + const DAYS = 1; + const WEEKS = 2; + const MONTHS = 3; + const YEARS = 4; + // Accounts Variable declaration let account_polymath; let account_issuer; @@ -137,7 +144,7 @@ contract("ScheduledCheckpoint", async (accounts) => { const log = (await I_SecurityToken.getPastEvents('ModuleAdded', {filter: {transactionHash: tx.transactionHash}}))[0]; // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(log.args._types[0].toString(), 2); assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); }); @@ -151,8 +158,8 @@ contract("ScheduledCheckpoint", async (accounts) => { it("Should successfully attach the ScheduledCheckpoint with the security token", async () => { await I_SecurityToken.changeGranularity(1, { from: token_owner }); const tx = await I_SecurityToken.addModule(I_ScheduledCheckpointFactory.address, "0x0", new BN(0), new BN(0), { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), 4, "ScheduledCheckpoint doesn't get deployed"); - assert.equal(tx.logs[2].args._types[1].toNumber(), 2, "ScheduledCheckpoint doesn't get deployed"); + assert.equal(tx.logs[2].args._types[0].toString(), 4, "ScheduledCheckpoint doesn't get deployed"); + assert.equal(tx.logs[2].args._types[1].toString(), 2, "ScheduledCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), "ScheduledCheckpoint", @@ -163,11 +170,12 @@ contract("ScheduledCheckpoint", async (accounts) => { let startTime; let interval; + let timeUnit = SECONDS; it("Should create a daily checkpoint", async () => { startTime = await latestTime() + 100; interval = 24 * 60 * 60; console.log("Creating scheduled CP: " + startTime, interval); - await I_ScheduledCheckpoint.addSchedule(web3.utils.fromAscii("CP1"), startTime, interval, { from: token_owner }); + await I_ScheduledCheckpoint.addSchedule(web3.utils.fromAscii("CP1"), startTime, interval, timeUnit, { from: token_owner }); console.log("2: " + await latestTime()); }); @@ -217,9 +225,9 @@ contract("ScheduledCheckpoint", async (accounts) => { it("Should have checkpoint created with correct balances", async () => { let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); - checkSchedule(cp1, "CP1", startTime, startTime + interval, interval, [1], [startTime], [1]); - assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toNumber(), 0); + checkSchedule(cp1, web3.utils.fromAscii("CP1"), startTime, startTime + interval, interval, timeUnit, [1], [startTime], [1]); + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); }); it("Should Buy some more tokens for account_investor2", async () => { @@ -254,9 +262,9 @@ contract("ScheduledCheckpoint", async (accounts) => { it("No additional checkpoints created", async () => { let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); - checkSchedule(cp1, "CP1", startTime, startTime + interval, interval, [1], [startTime], [1]); - assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toNumber(), 0); + checkSchedule(cp1, web3.utils.fromAscii("CP1"), startTime, startTime + interval, interval, timeUnit, [1], [startTime], [1]); + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); }); it("Add a new token holder - account_investor3", async () => { @@ -292,15 +300,15 @@ contract("ScheduledCheckpoint", async (accounts) => { it("Should have new checkpoint created with correct balances", async () => { let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); - checkSchedule(cp1, "CP1", startTime, startTime + 2 * interval, interval, [1, 2], [startTime, startTime + interval], [1, 1]); - assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toNumber(), 0); + checkSchedule(cp1, web3.utils.fromAscii("CP1"), startTime, startTime + 2 * interval, interval, timeUnit, [1, 2], [startTime, startTime + interval], [1, 1]); + assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor2, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); - assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor1, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); @@ -314,26 +322,27 @@ contract("ScheduledCheckpoint", async (accounts) => { let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); checkSchedule( cp1, - "CP1", + web3.utils.fromAscii("CP1"), startTime, startTime + 4 * interval, interval, + timeUnit, [1, 2, 3], [startTime, startTime + interval, startTime + 2 * interval], [1, 1, 2] ); - assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor3, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); - assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor2, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor2, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); - assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor1, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor1, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); }); @@ -345,28 +354,29 @@ contract("ScheduledCheckpoint", async (accounts) => { let cp1 = await I_ScheduledCheckpoint.getSchedule(web3.utils.fromAscii("CP1")); checkSchedule( cp1, - "CP1", + web3.utils.fromAscii("CP1"), startTime, startTime + 5 * interval, interval, + timeUnit, [1, 2, 3, 4], [startTime, startTime + interval, startTime + 2 * interval, startTime + 4 * interval], [1, 1, 2, 1] ); - assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 1)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor3, 2)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor3, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor3, 4)).toString(), new BN(web3.utils.toWei("1.5", "ether")).toString()); - assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor2, 1)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor2, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor2, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor2, 4)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); - assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toNumber(), 0); - assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toNumber(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 0)).toString(), 0); + assert.equal((await stGetter.balanceOfAt(account_investor1, 1)).toString(), 0); assert.equal((await stGetter.balanceOfAt(account_investor1, 2)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor1, 3)).toString(), new BN(web3.utils.toWei("1", "ether")).toString()); assert.equal((await stGetter.balanceOfAt(account_investor1, 4)).toString(), new BN(web3.utils.toWei("0.5", "ether")).toString()); @@ -376,24 +386,454 @@ contract("ScheduledCheckpoint", async (accounts) => { let perm = await I_ScheduledCheckpoint.getPermissions.call(); assert.equal(perm.length, 0); }); + + it("Remove daily checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(web3.utils.fromAscii("CP1"), {from: token_owner}); + }); + + }); + + describe("Tests for monthly scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-M-1"); + let startTime; + let interval = 5; + let timeUnit = MONTHS; + + it("Should create a monthly checkpoint", async () => { + startTime = new BN(await latestTime()).add(new BN(100)); + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one monthly checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5]; + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6]; + let timestamps = [startTime, addMonths(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6, 7]; + let timestamps = [startTime, addMonths(startTime, interval), addMonths(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6, 7, 8]; + let timestamps = [startTime, addMonths(startTime, interval), addMonths(startTime, interval * 2), addMonths(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five monthly checkpoints", async() => { + await increaseTime(duration.days(31 * interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [5, 6, 7, 8, 9]; + let timestamps = [startTime, addMonths(startTime, interval), addMonths(startTime, interval * 2), addMonths(startTime, interval * 4), addMonths(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addMonths(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove monthly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for yearly scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-Y-1"); + let startTime; + let interval = 3; + let timeUnit = YEARS; + + it("Should create a yearly checkpoint", async () => { + startTime = await latestTime() + 100; + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one yearly checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10]; + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11]; + let timestamps = [startTime, addYears(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11, 12]; + let timestamps = [startTime, addYears(startTime, interval), addYears(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11, 12, 13]; + let timestamps = [startTime, addYears(startTime, interval), addYears(startTime, interval * 2), addYears(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five yearly checkpoints", async() => { + await increaseTime(duration.days(366 * interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [10, 11, 12, 13, 14]; + let timestamps = [startTime, addYears(startTime, interval), addYears(startTime, interval * 2), addYears(startTime, interval * 4), addYears(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addYears(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove monthly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for monthly scheduled checkpoints -- end of month", async() => { + let name = web3.utils.fromAscii("CP-M-2"); + let previousTime; + let startDate; + let startTime; + let interval = 1; + let timeUnit = MONTHS; + + it("Should create a monthly checkpoint -- December 31", async () => { + previousTime = await latestTime(); + + startDate = new Date(previousTime * 1000); + startDate.setUTCMonth(11, 31); + startTime = startDate.getTime() / 1000; + console.log("previousTime:" + previousTime); + console.log("startTime:" + startTime); + console.log("startDate:" + startDate.toUTCString()); + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check monthly checkpoint -- January 31", async() => { + await increaseTime(startTime - previousTime + 100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [15]; + let nextTime = addMonths(startTime, interval); + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods); + }); + + function getDaysInFebruary() { + let days; + if ((startDate.getUTCFullYear() + 1) % 4 === 0) { + days = 29; + } else { + days = 28; + } + return days; + } + + function getEndOfFebruary(startTime, days) { + return setDate(addYears(startTime, 1), 1, days); //addMonths(startTime, interval * 2) + } + + it("Check monthly checkpoints -- February 28/29", async() => { + await increaseTime(duration.days(31 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [15, 16]; + let days = getDaysInFebruary(); + let nextTime = getEndOfFebruary(startTime, days); + let timestamps = [startTime, addMonths(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check monthly checkpoints -- March 31", async() => { + let days = getDaysInFebruary(); + await increaseTime(duration.days(days * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [15, 16, 17]; + let nextTime = addMonths(startTime, interval * 3); + let timestamps = [startTime, addMonths(startTime, interval), getEndOfFebruary(startTime, days)]; + let periods = [1, 1, 1]; + + for (let i = 0; i < timestamps.length; i++) { + assert.equal(schedule[6][i].toString(), timestamps[i]); + console.log(new Date(schedule[6][i].toString() * 1000).toUTCString()); + } + console.log("expected:" + new Date(nextTime * 1000).toUTCString()); + console.log("actual:" + new Date(schedule[2].toString() * 1000).toUTCString()); + checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove monthly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for daily scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-D-1"); + let startTime; + let interval = 13; + let timeUnit = DAYS; + + it("Should create a daily checkpoint", async () => { + startTime = await latestTime() + 100; + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one daily checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18]; + let timestamps = [startTime]; + let periods = [1]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two daily checkpoints", async() => { + await increaseTime(duration.days(interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19]; + let timestamps = [startTime, addDays(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three daily checkpoints", async() => { + await increaseTime(duration.days(interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19, 20]; + let timestamps = [startTime, addDays(startTime, interval), addDays(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four daily checkpoints", async() => { + await increaseTime(duration.days(interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19, 20, 21]; + let timestamps = [startTime, addDays(startTime, interval), addDays(startTime, interval * 2), addDays(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five daily checkpoints", async() => { + await increaseTime(duration.days(interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [18, 19, 20, 21, 22]; + let timestamps = [startTime, addDays(startTime, interval), addDays(startTime, interval * 2), addDays(startTime, interval * 4), addDays(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addDays(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove daily checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + + }); + + describe("Tests for weekly scheduled checkpoints", async() => { + + let name = web3.utils.fromAscii("CP-M-1"); + let startTime; + let interval = 9; + let timeUnit = WEEKS; + + it("Should create a weekly checkpoint", async () => { + startTime = await latestTime() + 100; + + let tx = await I_ScheduledCheckpoint.addSchedule(name, startTime, interval, timeUnit, {from: token_owner}); + checkScheduleLog(tx.logs[0], name, startTime, interval, timeUnit); + }); + + it("Check one weekly checkpoint", async() => { + await increaseTime(100); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23]; + let timestamps = [startTime]; + let periods = [1]; + + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check two weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24]; + let timestamps = [startTime, addWeeks(startTime, interval)]; + let periods = [1, 1]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 2), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check three weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval * 2)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24, 25]; + let timestamps = [startTime, addWeeks(startTime, interval), addWeeks(startTime, interval * 2)]; + let periods = [1, 1, 2]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 4), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check four weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24, 25, 26]; + let timestamps = [startTime, addWeeks(startTime, interval), addWeeks(startTime, interval * 2), addWeeks(startTime, interval * 4)]; + let periods = [1, 1, 2, 1]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 5), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Check five weekly checkpoints", async() => { + await increaseTime(duration.days(7 * interval * 3)); + await I_ScheduledCheckpoint.updateAll({from: token_owner}); + + let schedule = await I_ScheduledCheckpoint.getSchedule(name); + let checkpoints = [23, 24, 25, 26, 27]; + let timestamps = [startTime, addWeeks(startTime, interval), addWeeks(startTime, interval * 2), addWeeks(startTime, interval * 4), addWeeks(startTime, interval * 5)]; + let periods = [1, 1, 2, 1, 3]; + checkSchedule(schedule, name, startTime, addWeeks(startTime, interval * 8), interval, timeUnit, checkpoints, timestamps, periods); + }); + + it("Remove weekly checkpoint", async () => { + await I_ScheduledCheckpoint.removeSchedule(name, {from: token_owner}); + }); + }); + }); -function checkSchedule(schedule, name, startTime, nextTime, interval, checkpoints, timestamps, periods) { - assert.equal(web3.utils.toAscii(schedule[0]).replace(/\u0000/g, ""), name); - assert.equal(schedule[1].toNumber(), startTime); - assert.equal(schedule[2].toNumber(), nextTime); - assert.equal(schedule[3].toNumber(), interval); - assert.equal(schedule[4].length, checkpoints.length); +function setDate(time, month, day) { + let startDate = new Date(time * 1000); + startDate.setUTCMonth(month, day); + return startDate.getTime() / 1000; +} + +function addDays(timestamp, days) { + let time = new Date(timestamp * 1000); + return time.setUTCDate(time.getUTCDate() + days) / 1000; +} + +function addWeeks(timestamp, weeks) { + return addDays(timestamp, weeks * 7); +} + +function addMonths(timestamp, months) { + let time = new Date(timestamp * 1000); + return time.setUTCMonth(time.getUTCMonth() + months) / 1000; +} + +function addYears(timestamp, years) { + let time = new Date(timestamp * 1000); + return time.setUTCFullYear(time.getUTCFullYear() + years) / 1000; +} + +function checkScheduleLog(log, name, startTime, interval, timeUnit) { + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), web3.utils.toAscii(name)); + assert.equal(log.args._startTime.toString(), startTime.toString()); + assert.equal(log.args._interval.toString(), interval.toString()); + assert.equal(log.args._timeUint.toString(), timeUnit.toString()); +} + +function checkSchedule(schedule, name, startTime, nextTime, interval, timeUnit, checkpoints, timestamps, periods) { + assert.equal(web3.utils.toAscii(schedule[0]).replace(/\u0000/g, ""), web3.utils.toAscii(name)); + assert.equal(schedule[1].toString(), startTime.toString()); + assert.equal(schedule[2].toString(), nextTime.toString()); + assert.equal(schedule[3].toString(), interval.toString()); + assert.equal(schedule[4].toString(), timeUnit.toString()); + assert.equal(schedule[5].length, checkpoints.length); for (let i = 0; i < checkpoints.length; i++) { - assert.equal(schedule[4][i].toNumber(), checkpoints[i]); + assert.equal(schedule[5][i].toString(), checkpoints[i].toString()); } - assert.equal(schedule[5].length, timestamps.length); + assert.equal(schedule[6].length, timestamps.length); for (let i = 0; i < timestamps.length; i++) { - assert.equal(schedule[5][i].toNumber(), timestamps[i]); + assert.equal(schedule[6][i].toString(), timestamps[i].toString()); } - assert.equal(schedule[6].length, periods.length); + assert.equal(schedule[7].length, periods.length); + let totalPeriods = 0; for (let i = 0; i < periods.length; i++) { - assert.equal(schedule[6][i].toNumber(), periods[i]); + assert.equal(schedule[7][i].toString(), periods[i].toString()); + totalPeriods += periods[i]; } + assert.equal(schedule[8].toString(), totalPeriods.toString()); } diff --git a/test/zb_signed_transfer_manager.js b/test/zb_signed_transfer_manager.js index 7dbb4f2e6..f7dec103a 100644 --- a/test/zb_signed_transfer_manager.js +++ b/test/zb_signed_transfer_manager.js @@ -15,7 +15,6 @@ const SignedTransferManager = artifacts.require("./SignedTransferManager"); const STGetter = artifacts.require("./STGetter.sol"); const Web3 = require("web3"); -const BigNumber = require("bignumber.js"); let BN = Web3.utils.BN; const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port