From d7abab239a3afdcf5df5c44d51f283c1bb992a6c Mon Sep 17 00:00:00 2001 From: satyam Date: Tue, 13 Nov 2018 18:18:30 +0530 Subject: [PATCH 01/15] revamp the lockup --- .../LockupVolumeRestrictionTM.sol | 440 +++++++++--------- 1 file changed, 213 insertions(+), 227 deletions(-) diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol index 80f44cdb6..514f9f18f 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol @@ -3,7 +3,6 @@ pragma solidity ^0.4.24; import "./../../TransferManager/ITransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; - contract LockupVolumeRestrictionTM is ITransferManager { using SafeMath for uint256; @@ -13,11 +12,10 @@ contract LockupVolumeRestrictionTM is ITransferManager { // a per-user lockup struct LockUp { - uint lockUpPeriodSeconds; // total period of lockup (seconds) - uint releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) - uint startTime; // when this lockup starts (seconds) - uint totalAmount; // total amount of locked up tokens - uint alreadyWithdrawn; // amount already withdrawn for this lockup + uint256 lockupAmount; // Amount to be locked + uint256 startTime; // when this lockup starts (seconds) + uint256 lockUpPeriodSeconds; // total period of lockup (seconds) + uint256 releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) } // maps user addresses to an array of lockups for that user @@ -25,29 +23,32 @@ contract LockupVolumeRestrictionTM is ITransferManager { event AddNewLockUp( address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed addedIndex + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + uint256 indexed lockupIndex ); event RemoveLockUp( address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed removedIndex + uint256 indexed lockupIndex ); event ModifyLockUp( address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed modifiedIndex + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + uint256 indexed lockupIndex + ); + + event ChangeLockupIndex( + address indexed _userAddress, + uint256 indexed _oldLockupIndex, + uint256 indexed _newLockupIndex, + uint256 _timestamp ); /** @@ -61,17 +62,16 @@ contract LockupVolumeRestrictionTM is ITransferManager { { } - /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred * @param _from Address of the sender * @param _amount The amount of tokens to transfer * @param _isTransfer Whether or not this is an actual transfer or just a test to see if the tokens would be transferrable */ - function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool _isTransfer) public returns(Result) { + function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool /*_isTransfer*/) public returns(Result) { // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user - if (!paused && _from != address(0) && lockUps[_from].length != 0) { + if (!paused && _from != address(0) && lockUps[_from].length != 0) { // check if this transfer is valid - return _checkIfValidTransfer(_from, _amount, _isTransfer); + return _checkIfValidTransfer(_from, _amount); } return Result.NA; } @@ -79,64 +79,65 @@ contract LockupVolumeRestrictionTM is ITransferManager { /** * @notice Lets the admin create a volume restriction lockup for a given address. * @param _userAddress Address of the user whose tokens should be locked up + * @param _lockupAmount Amount of tokens that need to lock. + * @param _startTime When this lockup starts (seconds) * @param _lockUpPeriodSeconds Total period of lockup (seconds) * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param _startTime When this lockup starts (seconds) - * @param _totalAmount Total amount of locked up tokens */ function addLockUp( address _userAddress, - uint _lockUpPeriodSeconds, - uint _releaseFrequencySeconds, - uint _startTime, - uint _totalAmount - ) public withPerm(ADMIN) { - uint256 startTime = _startTime; - _checkLockUpParams(_lockUpPeriodSeconds, _releaseFrequencySeconds, _totalAmount); - - // if a startTime of 0 is passed in, then start now. - if (startTime == 0) { - /*solium-disable-next-line security/no-block-members*/ - startTime = now; - } - - lockUps[_userAddress].push(LockUp(_lockUpPeriodSeconds, _releaseFrequencySeconds, startTime, _totalAmount, 0)); + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds + ) + public + withPerm(ADMIN) + { + /*solium-disable-next-line security/no-block-members*/ + require(_startTime >= now, "start time is in past"); + _checkLockUpParams(_userAddress, _lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); + + lockUps[_userAddress].push(LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds)); emit AddNewLockUp( _userAddress, + _lockupAmount, + _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds, - startTime, - _totalAmount, - lockUps[_userAddress].length - 1 + lockUps[_userAddress].length -1 ); } /** * @notice Lets the admin create multiple volume restriction lockups for multiple given addresses. * @param _userAddresses Array of address of the user whose tokens should be locked up + * @param _lockupAmounts Array of the amounts that need to be locked for the different addresses. + * @param _startTimes Array of When this lockup starts (seconds) * @param _lockUpPeriodsSeconds Array of total periods of lockup (seconds) * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) - * @param _startTimes Array of When this lockup starts (seconds) - * @param _totalAmounts Array of total amount of locked up tokens */ function addLockUpMulti( address[] _userAddresses, - uint[] _lockUpPeriodsSeconds, - uint[] _releaseFrequenciesSeconds, - uint[] _startTimes, - uint[] _totalAmounts - ) external withPerm(ADMIN) { + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds + ) + external + withPerm(ADMIN) + { require( _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _startTimes.length && - _userAddresses.length == _totalAmounts.length, + _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _lockupAmounts.length, "Input array length mismatch" ); for (uint i = 0; i < _userAddresses.length; i++) { - addLockUp(_userAddresses[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i], _startTimes[i], _totalAmounts[i]); + addLockUp(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i]); } } @@ -144,76 +145,129 @@ contract LockupVolumeRestrictionTM is ITransferManager { /** * @notice Lets the admin remove a user's lock up * @param _userAddress Address of the user whose tokens are locked up - * @param _lockUpIndex The index of the LockUp to remove for the given userAddress + * @param _lockupIndex Index of the lockup need to be removed. */ - function removeLockUp(address _userAddress, uint _lockUpIndex) public withPerm(ADMIN) { - LockUp[] storage userLockUps = lockUps[_userAddress]; - require(_lockUpIndex < userLockUps.length, "Array out of bounds exception"); - - LockUp memory toRemove = userLockUps[_lockUpIndex]; + function removeLockUp(address _userAddress, uint256 _lockupIndex) public withPerm(ADMIN) { + require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); + LockUp[] storage userLockup = lockUps[_userAddress]; emit RemoveLockUp( _userAddress, - toRemove.lockUpPeriodSeconds, - toRemove.releaseFrequencySeconds, - toRemove.startTime, - toRemove.totalAmount, - _lockUpIndex + _lockupIndex ); - if (_lockUpIndex < userLockUps.length - 1) { + if (_lockupIndex != userLockup.length - 1) { // move the last element in the array into the index that is desired to be removed. - userLockUps[_lockUpIndex] = userLockUps[userLockUps.length - 1]; + userLockup[_lockupIndex] = userLockup[userLockup.length - 1]; + /*solium-disable-next-line security/no-block-members*/ + emit ChangeLockupIndex(_userAddress, userLockup.length - 1, _lockupIndex, now); + } + userLockup.length--; + } + + /** + * @notice Use to remove the lockup for multiple users + * @param _userAddresses Array of addresses of the user whose tokens are locked up + * @param _lockupIndexes Array of the indexes to the lockup that needs to be removed. + */ + function removeLockUpMulti(address[] _userAddresses, uint256[] _lockupIndexes) external withPerm(ADMIN) { + require(_userAddresses.length == _lockupIndexes.length, "Array length mismatch"); + for (uint i = 0; i < _userAddresses.length; i++) { + removeLockUp(_userAddresses[i], _lockupIndexes[i]); } - // delete the last element - userLockUps.length--; } /** * @notice Lets the admin modify a volume restriction lockup for a given address. * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockUpIndex The index of the LockUp to edit for the given userAddress + * @param _lockupAmount Amount of tokens that needs to be locked + * @param _startTime When this lockup starts (seconds) * @param _lockUpPeriodSeconds Total period of lockup (seconds) * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param _startTime When this lockup starts (seconds) - * @param _totalAmount Total amount of locked up tokens + * @param _lockupIndex Index of the lockup that needs to be modified. */ function modifyLockUp( address _userAddress, - uint _lockUpIndex, - uint _lockUpPeriodSeconds, - uint _releaseFrequencySeconds, - uint _startTime, - uint _totalAmount - ) public withPerm(ADMIN) { - require(_lockUpIndex < lockUps[_userAddress].length, "Array out of bounds exception"); - - uint256 startTime = _startTime; - // if a startTime of 0 is passed in, then start now. - if (startTime == 0) { + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + uint256 _lockupIndex + ) + public + withPerm(ADMIN) + { + require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); + + // Get the lockup from the master list and edit it + LockUp[] storage userLockup = lockUps[_userAddress]; + // If _startTime is equal to the previous startTime then it only allow to modify + // when there is no tokens gets unlocked from the lockup + if (_startTime == userLockup[_lockupIndex].startTime) { + require(_getUnlockedAmountForLockup(userLockup, _lockupIndex) == uint256(0)); + } else { /*solium-disable-next-line security/no-block-members*/ - startTime = now; + require(_startTime >= now, "start time is in past"); } - - _checkLockUpParams(_lockUpPeriodSeconds, _releaseFrequencySeconds, _totalAmount); - - // Get the lockup from the master list and edit it - lockUps[_userAddress][_lockUpIndex] = LockUp( + _checkLockUpParams( + _userAddress, + _lockupAmount, _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - lockUps[_userAddress][_lockUpIndex].alreadyWithdrawn + _releaseFrequencySeconds ); + userLockup[_lockupIndex] = LockUp( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + emit ModifyLockUp( _userAddress, + _lockupAmount, + _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds, - startTime, - _totalAmount, - _lockUpIndex + _lockupIndex + ); + } + + /** + * @notice Lets the admin modify a volume restriction lockup for a multiple address. + * @param _userAddresses Array of address of the user whose tokens should be locked up + * @param _lockupAmounts Array of the amount of tokens that needs to be locked for the respective addresses. + * @param _startTimes Array of the start time of the lockups (seconds) + * @param _lockUpPeriodsSeconds Array of unix timestamp for the list of lockups (seconds). + * @param _releaseFrequenciesSeconds How often to release a tranche of tokens (seconds) + * @param _lockupIndexes Array of the lockup indexes that needs to be modified + */ + function modifyLockUpMulti( + address[] _userAddresses, + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + uint256[] _lockupIndexes + ) public withPerm(ADMIN) { + require( + _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _lockupAmounts.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _lockupIndexes.length, + "Input array length mismatch" ); + for (uint256 i = 0; i < _userAddresses.length; i++) { + modifyLockUp( + _userAddresses[i], + _lockupAmounts[i], + _startTimes[i], + _lockUpPeriodsSeconds[i], + _releaseFrequenciesSeconds[i], + _lockupIndexes[i] + ); + } } /** @@ -229,164 +283,96 @@ contract LockupVolumeRestrictionTM is ITransferManager { * @param _userAddress Address of the user whose tokens should be locked up * @param _lockUpIndex The index of the LockUp to edit for the given userAddress */ - function getLockUp( - address _userAddress, - uint _lockUpIndex) - public view returns ( - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint alreadyWithdrawn - ) { - require(_lockUpIndex < lockUps[_userAddress].length, "Array out of bounds exception"); - LockUp storage userLockUp = lockUps[_userAddress][_lockUpIndex]; + function getLockUp( address _userAddress, uint _lockUpIndex) public view returns ( + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + uint256 unlockedAmount + ) { + require(lockUps[_userAddress].length > _lockUpIndex, "Invalid index"); + LockUp[] memory userLockup = lockUps[_userAddress]; return ( - userLockUp.lockUpPeriodSeconds, - userLockUp.releaseFrequencySeconds, - userLockUp.startTime, - userLockUp.totalAmount, - userLockUp.alreadyWithdrawn + userLockup[_lockUpIndex].lockupAmount, + userLockup[_lockUpIndex].startTime, + userLockup[_lockUpIndex].lockUpPeriodSeconds, + userLockup[_lockUpIndex].releaseFrequencySeconds, + _getUnlockedAmountForLockup(userLockup, _lockUpIndex) ); } /** - * @notice Takes a userAddress as input, and returns a uint that represents the number of tokens allowed to be withdrawn right now - * @param userAddress Address of the user whose lock ups should be checked + * @notice Checks whether the transfer is allowed + * @param _userAddress Address of the user whose lock ups should be checked + * @param _amount Amount of tokens that need to transact */ - function _checkIfValidTransfer(address userAddress, uint amount, bool isTransfer) internal returns (Result) { - // get lock up array for this user - LockUp[] storage userLockUps = lockUps[userAddress]; - - // maps the index of userLockUps to the amount allowed in this transfer - uint[] memory allowedAmountPerLockup = new uint[](userLockUps.length); - - uint[3] memory tokenSums = [ - uint256(0), // allowed amount right now - uint256(0), // total locked up, ever - uint256(0) // already withdrawn, ever - ]; - - // loop over the user's lock ups - for (uint i = 0; i < userLockUps.length; i++) { - LockUp storage aLockUp = userLockUps[i]; - - uint allowedAmountForThisLockup = 0; - - // check if lockup has entirely passed - /*solium-disable-next-line security/no-block-members*/ - if (now >= aLockUp.startTime.add(aLockUp.lockUpPeriodSeconds)) { - // lockup has passed, or not started yet. allow all. - allowedAmountForThisLockup = aLockUp.totalAmount.sub(aLockUp.alreadyWithdrawn); - /*solium-disable-next-line security/no-block-members*/ - } else if (now >= aLockUp.startTime) { - // lockup is active. calculate how many to allow to be withdrawn right now - // calculate how many periods have elapsed already - /*solium-disable-next-line security/no-block-members*/ - uint elapsedPeriods = (now.sub(aLockUp.startTime)).div(aLockUp.releaseFrequencySeconds); - // calculate the total number of periods, overall - uint totalPeriods = aLockUp.lockUpPeriodSeconds.div(aLockUp.releaseFrequencySeconds); - // calculate how much should be released per period - uint amountPerPeriod = aLockUp.totalAmount.div(totalPeriods); - // calculate the number of tokens that should be released, - // multiplied by the number of periods that have elapsed already - // and add it to the total tokenSums[0] - allowedAmountForThisLockup = amountPerPeriod.mul(elapsedPeriods).sub(aLockUp.alreadyWithdrawn); - - } - // tokenSums[0] is allowed sum - tokenSums[0] = tokenSums[0].add(allowedAmountForThisLockup); - // tokenSums[1] is total locked up - tokenSums[1] = tokenSums[1].add(aLockUp.totalAmount); - // tokenSums[2] is total already withdrawn - tokenSums[2] = tokenSums[2].add(aLockUp.alreadyWithdrawn); - - allowedAmountPerLockup[i] = allowedAmountForThisLockup; - } - - // tokenSums[0] is allowed sum - if (amount <= tokenSums[0]) { - // transfer is valid and will succeed. - if (!isTransfer) { - // if this isn't a real transfer, don't subtract the withdrawn amounts from the lockups. it's a "read only" txn - return Result.VALID; - } - - // we are going to write the withdrawn balances back to the lockups, so make sure that the person calling this function is the securityToken itself, since its public - require(msg.sender == securityToken, "Sender is not securityToken"); - - // subtract amounts so they are now known to be withdrawen - for (i = 0; i < userLockUps.length; i++) { - aLockUp = userLockUps[i]; - - // tokenSums[0] is allowed sum - if (allowedAmountPerLockup[i] >= tokenSums[0]) { - aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(tokenSums[0]); - // we withdrew the entire tokenSums[0] from the lockup. We are done. - break; - } else { - // we have to split the tokenSums[0] across mutiple lockUps - aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(allowedAmountPerLockup[i]); - // subtract the amount withdrawn from this lockup - tokenSums[0] = tokenSums[0].sub(allowedAmountPerLockup[i]); - } - - } - return Result.VALID; + function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + LockUp[] memory userLockup = lockUps[_userAddress]; + uint256 totalRemainingLockedAmount = 0; + for (uint256 i = 0; i < userLockup.length; i++) { + // Find out the remaining locked amount for a given lockup + uint256 remainingLockedAmount = userLockup[i].lockupAmount.sub(_getUnlockedAmountForLockup(userLockup, i)); + // aggregating all the remaining locked amount for all the lockups for a given address + totalRemainingLockedAmount = totalRemainingLockedAmount.add(remainingLockedAmount); } - - return _checkIfUnlockedTokenTransferIsPossible(userAddress, amount, tokenSums[1], tokenSums[2]); - } - - function _checkIfUnlockedTokenTransferIsPossible( - address userAddress, - uint amount, - uint totalSum, - uint alreadyWithdrawnSum - ) internal view returns (Result) { - // the amount the user wants to withdraw is greater than their allowed amounts according to the lockups. however, if the user has like, 10 tokens, but only 4 are locked up, we should let the transfer go through for those 6 that aren't locked up - uint currentUserBalance = ISecurityToken(securityToken).balanceOf(userAddress); - uint stillLockedAmount = totalSum.sub(alreadyWithdrawnSum); - if (currentUserBalance >= stillLockedAmount && amount <= currentUserBalance.sub(stillLockedAmount)) { - // the user has more tokens in their balance than are actually locked up. they should be allowed to withdraw the difference + // Present balance of the user + uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); + if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { return Result.VALID; } return Result.INVALID; } + /** + * @notice Provide the unlock amount for the given lockup for a particular user + */ + function _getUnlockedAmountForLockup(LockUp[] userLockup, uint256 _lockupIndex) internal view returns (uint256) { + // Calculate the no. of periods for a lockup + uint256 noOfPeriods = (userLockup[_lockupIndex].lockUpPeriodSeconds).div(userLockup[_lockupIndex].releaseFrequencySeconds); + // Calculate the transaction time lies in which period + uint256 elapsedPeriod = (now.sub(userLockup[_lockupIndex].startTime)).div(userLockup[_lockupIndex].releaseFrequencySeconds); + // Calculate the allowed unlocked amount per period + uint256 amountPerPeriod = (userLockup[_lockupIndex].lockupAmount).div(noOfPeriods); + // Find out the unlocked amount for a given lockup + uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); + return unLockedAmount; + } /** * @notice Parameter checking function for creating or editing a lockup. This function will cause an exception if any of the parameters are bad. - * @param lockUpPeriodSeconds Total period of lockup (seconds) - * @param releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param totalAmount Total amount of locked up tokens + * @param _userAddress Address whom lockup is being applied + * @param _lockupAmount Amount that needs to be locked + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) */ - function _checkLockUpParams(uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint totalAmount) internal view { - require(lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); - require(releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); - require(totalAmount != 0, "totalAmount cannot be zero"); + function _checkLockUpParams( + address _userAddress, + uint256 _lockupAmount, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds + ) + internal + view + { + require(_userAddress != address(0), "Invalid address"); + require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); + require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); // check that the total amount to be released isn't too granular require( - totalAmount % ISecurityToken(securityToken).granularity() == 0, + _lockupAmount % ISecurityToken(securityToken).granularity() == 0, "The total amount to be released is more granular than allowed by the token" ); // check that releaseFrequencySeconds evenly divides lockUpPeriodSeconds require( - lockUpPeriodSeconds % releaseFrequencySeconds == 0, + _lockUpPeriodSeconds % _releaseFrequencySeconds == 0, "lockUpPeriodSeconds must be evenly divisible by releaseFrequencySeconds" ); - // check that totalPeriods evenly divides totalAmount - uint totalPeriods = lockUpPeriodSeconds.div(releaseFrequencySeconds); - require( - totalAmount % totalPeriods == 0, - "The total amount being locked up must be evenly divisible by the number of total periods" - ); - // make sure the amount to be released per period is not too granular for the token - uint amountPerPeriod = totalAmount.div(totalPeriods); + uint256 totalPeriods = _lockUpPeriodSeconds.div(_releaseFrequencySeconds); + uint256 amountPerPeriod = _lockupAmount.div(totalPeriods); require( amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, "The amount to be released per period is more granular than allowed by the token" From 21bd05a14777f0200a51ab4411ae6a7b241b6699 Mon Sep 17 00:00:00 2001 From: satyam Date: Wed, 14 Nov 2018 22:29:30 +0530 Subject: [PATCH 02/15] test suite addition --- .../LockupVolumeRestrictionTM.sol | 35 +- ...kup_volume_restriction_transfer_manager.js | 899 +++++++++++------- 2 files changed, 557 insertions(+), 377 deletions(-) diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol index 514f9f18f..5c7183ee2 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol @@ -65,7 +65,6 @@ contract LockupVolumeRestrictionTM is ITransferManager { /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred * @param _from Address of the sender * @param _amount The amount of tokens to transfer - * @param _isTransfer Whether or not this is an actual transfer or just a test to see if the tokens would be transferrable */ function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool /*_isTransfer*/) public returns(Result) { // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user @@ -136,7 +135,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { "Input array length mismatch" ); - for (uint i = 0; i < _userAddresses.length; i++) { + for (uint256 i = 0; i < _userAddresses.length; i++) { addLockUp(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i]); } @@ -172,7 +171,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { */ function removeLockUpMulti(address[] _userAddresses, uint256[] _lockupIndexes) external withPerm(ADMIN) { require(_userAddresses.length == _lockupIndexes.length, "Array length mismatch"); - for (uint i = 0; i < _userAddresses.length; i++) { + for (uint256 i = 0; i < _userAddresses.length; i++) { removeLockUp(_userAddresses[i], _lockupIndexes[i]); } } @@ -274,7 +273,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { * @notice Get the length of the lockups array for a specific user address * @param _userAddress Address of the user whose tokens should be locked up */ - function getLockUpsLength(address _userAddress) public view returns (uint) { + function getLockUpsLength(address _userAddress) public view returns (uint256) { return lockUps[_userAddress].length; } @@ -283,7 +282,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { * @param _userAddress Address of the user whose tokens should be locked up * @param _lockUpIndex The index of the LockUp to edit for the given userAddress */ - function getLockUp( address _userAddress, uint _lockUpIndex) public view returns ( + function getLockUp(address _userAddress, uint256 _lockUpIndex) public view returns ( uint256 lockupAmount, uint256 startTime, uint256 lockUpPeriodSeconds, @@ -327,15 +326,22 @@ contract LockupVolumeRestrictionTM is ITransferManager { * @notice Provide the unlock amount for the given lockup for a particular user */ function _getUnlockedAmountForLockup(LockUp[] userLockup, uint256 _lockupIndex) internal view returns (uint256) { - // Calculate the no. of periods for a lockup - uint256 noOfPeriods = (userLockup[_lockupIndex].lockUpPeriodSeconds).div(userLockup[_lockupIndex].releaseFrequencySeconds); - // Calculate the transaction time lies in which period - uint256 elapsedPeriod = (now.sub(userLockup[_lockupIndex].startTime)).div(userLockup[_lockupIndex].releaseFrequencySeconds); - // Calculate the allowed unlocked amount per period - uint256 amountPerPeriod = (userLockup[_lockupIndex].lockupAmount).div(noOfPeriods); - // Find out the unlocked amount for a given lockup - uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); - return unLockedAmount; + /*solium-disable-next-line security/no-block-members*/ + if (userLockup[_lockupIndex].startTime > now) { + return 0; + } else { + // Calculate the no. of periods for a lockup + uint256 noOfPeriods = (userLockup[_lockupIndex].lockUpPeriodSeconds).div(userLockup[_lockupIndex].releaseFrequencySeconds); + // Calculate the transaction time lies in which period + /*solium-disable-next-line security/no-block-members*/ + uint256 elapsedPeriod = (now.sub(userLockup[_lockupIndex].startTime)).div(userLockup[_lockupIndex].releaseFrequencySeconds); + // Calculate the allowed unlocked amount per period + uint256 amountPerPeriod = (userLockup[_lockupIndex].lockupAmount).div(noOfPeriods); + // Find out the unlocked amount for a given lockup + uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); + return unLockedAmount; + } + } /** @@ -357,6 +363,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { require(_userAddress != address(0), "Invalid address"); require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); + require(_lockupAmount != 0, "lockupAmount cannot be zero"); // check that the total amount to be released isn't too granular require( diff --git a/test/w_lockup_volume_restriction_transfer_manager.js b/test/w_lockup_volume_restriction_transfer_manager.js index fe06f3f5c..2e0db1844 100644 --- a/test/w_lockup_volume_restriction_transfer_manager.js +++ b/test/w_lockup_volume_restriction_transfer_manager.js @@ -7,7 +7,7 @@ import { catchRevert } from "./helpers/exceptions"; const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const VolumeRestrictionTransferManager = artifacts.require('./LockupVolumeRestrictionTM'); +const LockUpVolumeRestrictionTM = artifacts.require('./LockupVolumeRestrictionTM'); const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); const Web3 = require('web3'); @@ -33,13 +33,13 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { let message = "Transaction Should Fail!"; // Contract Instance Declaration - let P_VolumeRestrictionTransferManagerFactory; + let P_LockUpVolumeRestrictionTMFactory; let I_SecurityTokenRegistryProxy; - let P_VolumeRestrictionTransferManager; + let P_LockUpVolumeRestrictionTM; let I_GeneralTransferManagerFactory; - let I_VolumeRestrictionTransferManagerFactory; + let I_LockUpVolumeRestrictionTMFactory; let I_GeneralPermissionManager; - let I_VolumeRestrictionTransferManager; + let I_LockUpVolumeRestrictionTM; let I_GeneralTransferManager; let I_ModuleRegistryProxy; let I_ModuleRegistry; @@ -51,6 +51,9 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { let I_SecurityToken; let I_PolyToken; let I_PolymathRegistry; + let I_SecurityToken_div; + let I_GeneralTransferManager_div; + let I_LockUpVolumeRestrictionTM_div; // SecurityToken Details const name = "Team"; @@ -59,11 +62,16 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { const decimals = 18; const contact = "team@polymath.network"; + const name2 = "Core"; + const symbol2 = "Core"; + // Module key const delegateManagerKey = 1; const transferManagerKey = 2; const stoKey = 3; + let temp; + // Initial fee for ticker registry and security token registry const initRegFee = web3.utils.toWei("250"); @@ -94,10 +102,10 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { I_STRProxied ] = instances; - // STEP 4(c): Deploy the VolumeRestrictionTransferManager - [I_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); - // STEP 4(d): Deploy the VolumeRestrictionTransferManager - [P_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + // STEP 4(c): Deploy the LockUpVolumeRestrictionTMFactory + [I_LockUpVolumeRestrictionTMFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 4(d): Deploy the LockUpVolumeRestrictionTMFactory + [P_LockUpVolumeRestrictionTMFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); // Printing all the contract addresses console.log(` @@ -113,7 +121,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} LockupVolumeRestrictionTransferManagerFactory: - ${I_VolumeRestrictionTransferManagerFactory.address} + ${I_LockUpVolumeRestrictionTMFactory.address} ----------------------------------------------------------------------------- `); }); @@ -153,11 +161,46 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { I_GeneralTransferManager = GeneralTransferManager.at(moduleData); }); + + it("Should register another ticker before the generation of new security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol2, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol2.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name2, symbol2, tokenDetails, true, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol2.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken_div = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken_div.ModuleAdded({from: _blockNo}), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken_div.getModulesByType(2))[0]; + I_GeneralTransferManager_div = GeneralTransferManager.at(moduleData); + }); + + }); describe("Buy tokens using on-chain whitelist and test locking them up and attempting to transfer", async() => { - it("Should Buy the tokens", async() => { + it("Should Buy the tokens from non-divisible", async() => { // Add the Investor in to the whitelist let tx = await I_GeneralTransferManager.modifyWhitelist( @@ -184,7 +227,34 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ); }); - it("Should Buy some more tokens", async() => { + it("Should Buy the tokens from the divisible token", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager_div.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_SecurityToken_div.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken_div.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should Buy some more tokens from non-divisible tokens", async() => { // Add the Investor in to the whitelist let tx = await I_GeneralTransferManager.modifyWhitelist( @@ -208,38 +278,50 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ); }); - it("Should unsuccessfully attach the VolumeRestrictionTransferManager factory with the security token -- failed because Token is not paid", async () => { + it("Should unsuccessfully attach the LockUpVolumeRestrictionTM factory with the security token -- failed because Token is not paid", async () => { await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); await catchRevert( - I_SecurityToken.addModule(P_VolumeRestrictionTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_LockUpVolumeRestrictionTMFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) ) }); - it("Should successfully attach the VolumeRestrictionTransferManager factory with the security token", async () => { + it("Should successfully attach the LockUpVolumeRestrictionTM factory with the security token", async () => { let snapId = await takeSnapshot(); await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); - const tx = await I_SecurityToken.addModule(P_VolumeRestrictionTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); - assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "VolumeRestrictionTransferManagerFactory doesn't get deployed"); + const tx = await I_SecurityToken.addModule(P_LockUpVolumeRestrictionTMFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name) .replace(/\u0000/g, ''), "LockupVolumeRestrictionTM", - "VolumeRestrictionTransferManagerFactory module was not added" + "LockUpVolumeRestrictionTM module was not added" ); - P_VolumeRestrictionTransferManager = VolumeRestrictionTransferManager.at(tx.logs[3].args._module); + P_LockUpVolumeRestrictionTM = LockUpVolumeRestrictionTM.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); - it("Should successfully attach the VolumeRestrictionTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_VolumeRestrictionTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "VolumeRestrictionTransferManager doesn't get deployed"); + it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the non-divisible security token", async () => { + const tx = await I_SecurityToken.addModule(I_LockUpVolumeRestrictionTMFactory.address, 0, 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "LockupVolumeRestrictionTM", + "LockUpVolumeRestrictionTM module was not added" + ); + I_LockUpVolumeRestrictionTM = LockUpVolumeRestrictionTM.at(tx.logs[2].args._module); + }); + + it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the divisible security token", async () => { + const tx = await I_SecurityToken_div.addModule(I_LockUpVolumeRestrictionTMFactory.address, 0, 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name) .replace(/\u0000/g, ''), "LockupVolumeRestrictionTM", - "VolumeRestrictionTransferManager module was not added" + "LockUpVolumeRestrictionTM module was not added" ); - I_VolumeRestrictionTransferManager = VolumeRestrictionTransferManager.at(tx.logs[2].args._module); + I_LockUpVolumeRestrictionTM_div = LockUpVolumeRestrictionTM.at(tx.logs[2].args._module); }); it("Add a new token holder", async() => { @@ -267,12 +349,11 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }); it("Should pause the tranfers at transferManager level", async() => { - let tx = await I_VolumeRestrictionTransferManager.pause({from: token_owner}); + let tx = await I_LockUpVolumeRestrictionTM.pause({from: token_owner}); }); it("Should still be able to transfer between existing token holders up to limit", async() => { - // Add the Investor in to the whitelist - // Mint some tokens + // Transfer Some tokens between the investor await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); assert.equal( @@ -282,14 +363,23 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }); it("Should unpause the tranfers at transferManager level", async() => { - await I_VolumeRestrictionTransferManager.unpause({from: token_owner}); + await I_LockUpVolumeRestrictionTM.unpause({from: token_owner}); }); - it("Should prevent the creation of a lockup with bad parameters where the totalAmount is zero", async() => { + it("Should prevent the creation of a lockup with bad parameters where the lockupAmount is zero", async() => { // create a lockup - // this will generate an exception because the totalAmount is zero + // this will generate an exception because the lockupAmount is zero await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, 0, { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + 0, + latestTime() + + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + { + from: token_owner + } + ) ) }); @@ -297,7 +387,16 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup // this will generate an exception because the releaseFrequencySeconds is zero await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 0, 0, web3.utils.toWei('1', 'ether'), { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + 0, + { + from: token_owner + } + ) ); }); @@ -305,7 +404,16 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup // this will generate an exception because the lockUpPeriodSeconds is zero await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 0, 4, 0, web3.utils.toWei('1', 'ether'), { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime() + duration.seconds(1), + 0, + duration.seconds(100000), + { + from: token_owner + } + ) ); }); @@ -314,8 +422,49 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup // this will generate an exception because we're locking up 5e17 tokens but the granularity is 5e18 tokens await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, web3.utils.toWei('0.5', 'ether'), { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + web3.utils.toWei('0.5', 'ether'), + latestTime() + + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + { + from: token_owner + } + ) + ); + }); + + it("Should allow the creation of a lockup where the lockup amount is divisible" , async() => { + // create a lockup + let tx = await I_LockUpVolumeRestrictionTM_div.addLockUp( + account_investor1, + web3.utils.toWei('0.5', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + { + from: token_owner + } ); + assert.equal(tx.logs[0].args.userAddress, account_investor1); + assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('0.5', 'ether')); + }); + + it("Should allow the creation of a lockup where the lockup amount is prime no", async() => { + // create a lockup + let tx = await I_LockUpVolumeRestrictionTM_div.addLockUp( + account_investor1, + web3.utils.toWei('64951', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + { + from: token_owner + } + ); + assert.equal(tx.logs[0].args.userAddress, account_investor1); + assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); }); it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is not evenly divisible by releaseFrequencySeconds", async() => { @@ -328,17 +477,33 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // over 17 seconds total, with 4 periods. // this will generate an exception because 17 is not evenly divisble by 4. await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 17, 4, 0, balance, { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + web3.utils.toWei('4', 'ether'), + latestTime(), + 17, + 4, + { + from: token_owner + } + ) ); }); it("Should prevent the creation of a lockup with bad parameters where the total amount being locked up isn't evenly divisible by the number of total periods", async() => { // create a lockup for a balance of 1 eth - // over 16e18 seconds total, with 4e18 periods of 4 seconds each. - // this will generate an exception because 16e18 / 4e18 = 4e18 but the token granularity is 1e18 and 1e18 % 4e18 != 0 await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, web3.utils.toWei('16', 'ether'), 4, 0, web3.utils.toWei('1', 'ether'), { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime(), + duration.seconds(400000), + duration.seconds(100000), + { + from: token_owner + } + ) ); }); @@ -351,7 +516,16 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // over 16 seconds total, with 4 periods of 4 seconds each. // this will generate an exception because 9000000000000000000 / 4 = 2250000000000000000 but the token granularity is 1000000000000000000 await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, balance, { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + balance, + latestTime() + duration.seconds(1), + 16, + 4, + { + from: token_owner + } + ) ); }); @@ -362,445 +536,444 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // create a lockup for their entire balance // over 12 seconds total, with 3 periods of 4 seconds each. - await I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 12, 4, 0, balance, { from: token_owner }); - - // read only - check if transfer will pass. it should return INVALID - let result = await I_VolumeRestrictionTransferManager.verifyTransfer.call(account_investor2, account_investor1, web3.utils.toWei('1', 'ether'), 0, false) - // enum Result {INVALID, NA, VALID, FORCE_VALID} and we want VALID so it should be 2 - assert.equal(result.toString(), '0') - + await I_LockUpVolumeRestrictionTM.addLockUp( + account_investor2, + balance, + latestTime() + duration.seconds(1), + 60, + 20, + { + from: token_owner + } + ); + await increaseTime(2); + let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) ); }); - it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { - - // wait 4 seconds - await increaseTime(duration.seconds(4)); - - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3', 'ether'), { from: account_investor2 }); - }); - it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { - + // wait 20 seconds + await increaseTime(duration.seconds(20)); + let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) ); }); - it("Should allow the transfer of more tokens in a lockup if another period has passed", async() => { - - // wait 4 more seconds - await increaseTime(4000); - - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3', 'ether'), { from: account_investor2 }); + it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { + let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); - it("Should allow the transfer of all tokens in a lockup if the entire lockup has passed", async() => { - - let balance = await I_SecurityToken.balanceOf(account_investor2) - - // wait 4 more seconds - await increaseTime(4000); - - await I_SecurityToken.transfer(account_investor1, balance, { from: account_investor2 }); + it("Should again transfer of tokens in a lockup if a period has passed", async() => { + let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); - it("Should prevent the transfer of tokens in an edited lockup", async() => { + it("Should allow the transfer of more tokens in a lockup if another period has passed", async() => { + // wait 20 more seconds + 1 to get rid of same block time + await increaseTime(duration.seconds(21)); + let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); + }); - // balance here should be 12000000000000000000 (12e18 or 12 eth) - let balance = await I_SecurityToken.balanceOf(account_investor1) + it("Buy more tokens from secondary market to investor2", async() => { + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('5', 'ether'), { from: token_owner }); - // create a lockup for their entire balance - // over 16 seconds total, with 4 periods of 4 seconds each. - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 16, 4, 0, balance, { from: token_owner }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }) - // let blockNumber = await web3.eth.getBlockNumber(); - // console.log('blockNumber',blockNumber) - let now = (await web3.eth.getBlock('latest')).timestamp + it("Should allow transfer for the tokens that comes from secondary market + unlocked tokens", async() => { - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('6', 'ether') ); + }); - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 1) + it("Should allow the transfer of all tokens in a lockup if the entire lockup has passed", async() => { - let lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor1, 0); - // console.log(lockUp); - // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount - assert.equal(lockUp[0].toString(), '16'); - assert.equal(lockUp[1].toString(), '4'); - assert.equal(lockUp[2].toNumber(), now); - assert.equal(lockUp[3].toString(), balance.toString()); + let balance = await I_SecurityToken.balanceOf(account_investor2) - // edit the lockup - await I_VolumeRestrictionTransferManager.modifyLockUp(account_investor1, 0, 8, 4, 0, balance, { from: token_owner }); + // wait 20 more seconds + 1 to get rid of same block time + await increaseTime(duration.seconds(20)); - // attempt a transfer - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) + await I_SecurityToken.transfer(account_investor1, balance, { from: account_investor2 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('0', 'ether') ); - - // wait 4 seconds - await new Promise(resolve => setTimeout(resolve, 4000)); - - // transfer should succeed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }); - }); - it("Should succesfully modify the lockup - fail because array index out of bound", async() => { - // balance here should be 12000000000000000000 (12e18 or 12 eth) - let balance = await I_SecurityToken.balanceOf(account_investor1); + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_VolumeRestrictionTransferManager.modifyLockUp(account_investor1, 8, 8, 4, 0, balance, { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUpMulti( + [account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + { + from: token_owner + } + ) ); }) - it("Should succesfully get the lockup - fail because array index out of bound", async() => { + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_VolumeRestrictionTransferManager.getLockUp(account_investor1, 9) + I_LockUpVolumeRestrictionTM.addLockUpMulti( + [account_investor3, account_investor3], + [], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + { + from: token_owner + } + ) ); }) - it("Should be possible to remove a lockup -- couldn't transfer because of lock up", async() => { - - let acct1Balance = await I_SecurityToken.balanceOf(account_investor1) - - await catchRevert( - I_SecurityToken.transfer(account_investor2, acct1Balance, { from: account_investor1 }) - ); - - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 1) - - // remove the lockup - await I_VolumeRestrictionTransferManager.removeLockUp(account_investor1, 0, { from: token_owner }); - - lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 0) - - let acct2BalanceBefore = await I_SecurityToken.balanceOf(account_investor2) - await I_SecurityToken.transfer(account_investor2, acct1Balance, { from: account_investor1 }); - let acct2BalanceAfter = await I_SecurityToken.balanceOf(account_investor2) - - assert.equal(acct2BalanceAfter.sub(acct2BalanceBefore).toString(), acct1Balance.toString()) - }); - - it("Should try to remove the lockup --failed because of index is out of bounds", async() => { + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 7, { from: token_owner }) + I_LockUpVolumeRestrictionTM.addLockUpMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45, 50], + [20, 15], + { + from: token_owner + } + ) ); }) - it("Should be possible to create multiple lockups at once", async() => { - - let balancesBefore = {} - - // should be 12000000000000000000 - balancesBefore[account_investor2] = await I_SecurityToken.balanceOf(account_investor2) - - - // should be 10000000000000000000 - balancesBefore[account_investor3] = await I_SecurityToken.balanceOf(account_investor3) - - - let lockUpCountsBefore = {} - - // get lockups for acct 2 - lockUpCountsBefore[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCountsBefore[account_investor2], 1) // there's one old, expired lockup on acct already - - // get lockups for acct 3 - lockUpCountsBefore[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3); - assert.equal(lockUpCountsBefore[account_investor3], 0) - - // create lockups for their entire balances - await I_VolumeRestrictionTransferManager.addLockUpMulti( - [account_investor2, account_investor3], - [24, 8], - [4, 4], - [0, 0], - [balancesBefore[account_investor2], balancesBefore[account_investor3]], - { from: token_owner } + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpVolumeRestrictionTM.addLockUpMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45, 50], + [20, 15, 10], + { + from: token_owner + } + ) ); + }) - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }) + it("Should add the multiple lockup to a address", async() => { + await I_LockUpVolumeRestrictionTM.addLockUpMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + { + from: token_owner + } ); + await increaseTime(1); + let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor3, 0); + let tx2 = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor3, 1); + console.log("Total Amount get unlocked:", (tx[4].toNumber()) + (tx2[4].toNumber())); await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor3 }) + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor3 }) ); - let balancesAfter = {} - balancesAfter[account_investor2] = await I_SecurityToken.balanceOf(account_investor2) - assert.equal(balancesBefore[account_investor2].toString(), balancesAfter[account_investor2].toString()) - - balancesAfter[account_investor3] = await I_SecurityToken.balanceOf(account_investor3) - assert.equal(balancesBefore[account_investor3].toString(), balancesAfter[account_investor3].toString()) - - let lockUpCountsAfter = {} - - // get lockups for acct 2 - lockUpCountsAfter[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCountsAfter[account_investor2], 2); - - // get lockups for acct 3 - lockUpCountsAfter[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3); - assert.equal(lockUpCountsAfter[account_investor3], 1); - - // wait 4 seconds - await increaseTime(4000); - - // try transfers again - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor3 }); - - - balancesAfter[account_investor2] = await I_SecurityToken.balanceOf(account_investor2) - assert.equal(balancesBefore[account_investor2].sub(web3.utils.toWei('2', 'ether')).toString(), balancesAfter[account_investor2].toString()) + }); - balancesAfter[account_investor3] = await I_SecurityToken.balanceOf(account_investor3) - assert.equal(balancesBefore[account_investor3].sub(web3.utils.toWei('5', 'ether')).toString(), balancesAfter[account_investor3].toString()) + it("Should transfer the tokens after period get passed", async() => { + // increase 20 sec that makes 1 period passed + // 2 from a period and 1 is already unlocked + await increaseTime(21); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + }) + it("Should transfer the tokens after passing another period of the lockup", async() => { + // increase the 15 sec that makes first period of another lockup get passed + // allow 1 token to transfer + await increaseTime(15); + // first fail because 3 tokens are not in unlocked state + await catchRevert( + I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + ) + // second txn will pass because 1 token is in unlocked state + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1'), { from: account_investor3 }) }); - it("Should revert if the parameters are bad when creating multiple lockups", async() => { + it("Should transfer the tokens from both the lockup simultaneously", async() => { + // Increase the 20 sec (+ 1 to mitigate the block time) that unlocked another 2 tokens from the lockup 1 and simultaneously unlocked 1 + // more token from the lockup 2 + await increaseTime(21); - await catchRevert( - // pass in the wrong number of params. txn should revert - I_VolumeRestrictionTransferManager.addLockUpMulti( - [account_investor2, account_investor3], - [16, 8], - [2], // this array should have 2 elements but it has 1, which should cause a revert - [0, 0], - [web3.utils.toWei('1', 'ether'), web3.utils.toWei('1', 'ether')], - { from: token_owner } - ) + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('3', 'ether') ); }); - it("Should be possible to create a lockup with a specific start time in the future", async() => { - - // remove all lockups for account 2 - let lockUpsLength = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpsLength, 2); - await I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 0, { from: token_owner }); - await I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 0, { from: token_owner }); - lockUpsLength = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpsLength, 0); - - let now = latestTime(); - - // balance here should be 10000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor2) - - await I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 100, 10, now + duration.seconds(4), balance, { from: token_owner }); - - // wait 4 seconds for the lockup to begin - await increaseTime(duration.seconds(4)); - - // try another transfer. it should also fail because the lockup has just begun + it("Should remove multiple lockup --failed because of bad owner", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - + I_LockUpVolumeRestrictionTM.removeLockUpMulti( + [account_investor3, account_investor3], + [0,1], + { + from: account_polymath + } + ) + ); }); - it("Should be possible to edit a lockup with a specific start time in the future", async() => { - - // edit the lockup - let now = latestTime(); - - // should be 10000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor2) - - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCount, 1) - - let lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - - // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount - assert.equal(lockUp[0].toString(), '100'); - assert.equal(lockUp[1].toString(), '10'); - assert.isAtMost(lockUp[2].toNumber(), now); - assert.equal(lockUp[3].toString(), balance.toString()); - - // edit the lockup - await I_VolumeRestrictionTransferManager.modifyLockUp(account_investor2, 0, 8, 4, now + duration.seconds(4), balance, { from: token_owner }); - - // check and get the lockup again - lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCount, 1) - - lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - - // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount - assert.equal(lockUp[0].toString(), '8'); - assert.equal(lockUp[1].toString(), '4'); - assert.isAtMost(lockUp[2].toNumber(), now + 4); - assert.equal(lockUp[3].toString(), balance.toString()); - - // try a transfer. it should fail because again, the lockup hasn't started yet. + it("Should remove the multiple lockup -- failed because of invalid index", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); + I_LockUpVolumeRestrictionTM.removeLockUpMulti( + [account_investor3, account_investor3], + [0,3], + { + from: account_polymath + } + ) + ); + }) - // wait 4 seconds for the lockup to begin - await increaseTime(duration.seconds(4)); + it("Should remove the multiple lockup", async() => { + await I_LockUpVolumeRestrictionTM.removeLockUpMulti( + [account_investor3, account_investor3], + [1,0], + { + from: token_owner + } + ) + // do the free transaction now + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + }); - // try another transfer. it should fail because the lockup has just begun + it("Should fail to modify the lockup -- because of bad owner", async() => { + await I_SecurityToken.mint(account_investor3, web3.utils.toWei("9"), {from: token_owner}); + + let tx = await I_LockUpVolumeRestrictionTM.addLockUp( + account_investor3, + web3.utils.toWei("9"), + latestTime() + duration.minutes(5), + 60, + 20, + { + from: token_owner + } + ); + temp = (tx.logs[0].args.lockupIndex).toNumber(); await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - - // wait 4 seconds for the lockup's first period to elapse - await increaseTime(duration.seconds(4)); - - // try another transfer. it should pass - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 }); - + // edit the lockup + I_LockUpVolumeRestrictionTM.modifyLockUp( + account_investor3, + web3.utils.toWei("9"), + latestTime() + duration.seconds(1), + 60, + 20, + (tx.logs[0].args.lockupIndex).toNumber(), + { + from: account_polymath + } + ) + ) + }) - // try another transfer without waiting for another period to pass. it should fail + it("Modify the lockup when startTime is in past -- failed because startTime is in the past", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 }) - ); - - // wait 4 seconds for the lockup's first period to elapse - await increaseTime(duration.seconds(4)); - - let lockUpBeforeVerify = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - // check if transfer will pass in read-only operation - let result = await I_VolumeRestrictionTransferManager.verifyTransfer.call(account_investor2, account_investor1, web3.utils.toWei('5', 'ether'), 0, false) - // enum Result {INVALID, NA, VALID, FORCE_VALID} and we want VALID so it should be 2 - assert.equal(result.toString(), '2') - let lockUpAfterVerify = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - - assert.equal(lockUpBeforeVerify[4].toString(), lockUpAfterVerify[4].toString()) - - // try another transfer. it should pass - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 }); - - // wait 4 seconds for the lockup's first period to elapse. but, we are all out of periods. - await increaseTime(duration.seconds(4)); + // edit the lockup + I_LockUpVolumeRestrictionTM.modifyLockUp( + account_investor3, + web3.utils.toWei("9"), + latestTime() - duration.seconds(50), + 60, + 20, + temp, + { + from: token_owner + } + ) + ) + }) - // try one final transfer. this should fail because the user has already withdrawn their entire balance + it("Modify the lockup when startTime is in past -- failed because of invalid index", async() => { await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) + // edit the lockup + I_LockUpVolumeRestrictionTM.modifyLockUp( + account_investor3, + web3.utils.toWei("9"), + latestTime() + duration.seconds(50), + 60, + 20, + 6, + { + from: token_owner + } + ) + ) + }) + + it("should successfully modify the lockup", async() => { + // edit the lockup + await I_LockUpVolumeRestrictionTM.modifyLockUp( + account_investor3, + web3.utils.toWei("9"), + latestTime() + duration.seconds(50), + 60, + 20, + temp, + { + from: token_owner + } ); - }); + }) - it("Should be possible to stack lockups", async() => { - // should be 17000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor1) + it("Should prevent the transfer of tokens in an edited lockup", async() => { - // check and make sure that acct1 has no lockups so far - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount.toString(), 0) + // balance here should be 12000000000000000000 (12e18 or 12 eth) + let balance = await I_SecurityToken.balanceOf(account_investor1) - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 12, 4, 0, web3.utils.toWei('6', 'ether'), { from: token_owner }); + console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); - // try to transfer 11 tokens that aren't locked up yet be locked up. should succeed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('11', 'ether'), { from: account_investor1 }); + // create a lockup for their entire balance + // over 16 seconds total, with 4 periods of 4 seconds each. + await I_LockUpVolumeRestrictionTM.addLockUp( + account_investor1, + balance, + latestTime() + duration.minutes(5), + 60, + 20, + { + from: token_owner + } + ); - // try a transfer. it should fail because it's locked up from the first lockups await catchRevert( I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) ); - // wait 4 seconds for the lockup's first period to elapse. - await increaseTime(duration.seconds(4)); - - // should succeed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor1 }); - // send 8 back to investor1 so that we can lock them up - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('8', 'ether'), { from: account_investor2 }); - - // let's add another lockup to stack them - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 16, 4, 0, web3.utils.toWei('8', 'ether'), { from: token_owner }); + // check and get the lockup + let lockUpCount = await I_LockUpVolumeRestrictionTM.getLockUpsLength(account_investor1); + assert.equal(lockUpCount, 1) - // try a transfer. it should fail because it's locked up from both lockups - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) + let lockUp = await I_LockUpVolumeRestrictionTM.getLockUp(account_investor1, 0); + // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount + assert.equal( + lockUp[0].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber(), + balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber() ); + assert.equal(lockUp[2].toNumber(), 60); + assert.equal(lockUp[3].toNumber(), 20); + assert.equal(lockUp[4].toNumber(), 0); - // wait 4 seconds for the 1st lockup's second period to elapse, and the 2nd lockup's first period to elapse - await increaseTime(duration.seconds(4)); - - // should now be able to transfer 4, because of 2 allowed from the 1st lockup and 2 from the 2nd - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('4', 'ether'), { from: account_investor1 }); + // edit the lockup + temp = latestTime() + duration.seconds(1); + await I_LockUpVolumeRestrictionTM.modifyLockUp( + account_investor1, + balance, + temp, + 60, + 20, + 0, + { + from: token_owner + } + ); - // try aother transfer. it should fail because it's locked up from both lockups again + // attempt a transfer await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) ); - // wait 4 seconds for the 1st lockup's final period to elapse, and the 2nd lockup's second period to elapse - await increaseTime(duration.seconds(4)); - // should now be able to transfer 4, because of 2 allowed from the 1st lockup and 2 from the 2nd - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('4', 'ether'), { from: account_investor1 }); + // wait 20 seconds + await increaseTime(21); - // wait 8 seconds for 2nd lockup's third and fourth periods to elapse - await increaseTime(duration.seconds(8)); - // should now be able to transfer 4, because there are 2 allowed per period in the 2nd lockup, and 2 periods have elapsed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('4', 'ether'), { from: account_investor1 }); - - // send the 3 back from acct2 that we sent over in the beginning of this test - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3', 'ether'), { from: account_investor2 }); + // transfer should succeed + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }); - // try another transfer. it should pass because both lockups have been entirely used - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); + }); - balance = await I_SecurityToken.balanceOf(account_investor1) - assert.equal(balance.toString(), web3.utils.toWei('2', 'ether')) + it("Modify the lockup during the lockup periods --fail because already tokens get unlocked", async() => { + let balance = await I_SecurityToken.balanceOf(account_investor1) + let lockUp = await I_LockUpVolumeRestrictionTM.getLockUp(account_investor1, 0); + console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + // edit the lockup + await catchRevert( + I_LockUpVolumeRestrictionTM.modifyLockUp( + account_investor1, + balance, + temp, + 90, + 30, + 0, + { + from: token_owner + } + ) + ); }); + it("Should succesfully get the lockup - fail because array index out of bound", async() => { + await catchRevert( + I_LockUpVolumeRestrictionTM.getLockUp(account_investor1, 9) + ); + }) it("Should get configuration function signature", async() => { - let sig = await I_VolumeRestrictionTransferManager.getInitFunction.call(); + let sig = await I_LockUpVolumeRestrictionTM.getInitFunction.call(); assert.equal(web3.utils.hexToNumber(sig), 0); }); it("Should get the permission", async() => { - let perm = await I_VolumeRestrictionTransferManager.getPermissions.call(); + let perm = await I_LockUpVolumeRestrictionTM.getPermissions.call(); assert.equal(perm.length, 1); - // console.log(web3.utils.toAscii(perm[0]).replace(/\u0000/g, '')) assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ''), "ADMIN") }); }); - describe("VolumeRestriction Transfer Manager Factory test cases", async() => { + describe("LockUpVolumeRestrictionTM Transfer Manager Factory test cases", async() => { it("Should get the exact details of the factory", async() => { - assert.equal(await I_VolumeRestrictionTransferManagerFactory.getSetupCost.call(),0); - assert.equal((await I_VolumeRestrictionTransferManagerFactory.getTypes.call())[0],2); - assert.equal(web3.utils.toAscii(await I_VolumeRestrictionTransferManagerFactory.getName.call()) + assert.equal(await I_LockUpVolumeRestrictionTMFactory.getSetupCost.call(),0); + assert.equal((await I_LockUpVolumeRestrictionTMFactory.getTypes.call())[0],2); + assert.equal(web3.utils.toAscii(await I_LockUpVolumeRestrictionTMFactory.getName.call()) .replace(/\u0000/g, ''), "LockupVolumeRestrictionTM", "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.description.call(), + assert.equal(await I_LockUpVolumeRestrictionTMFactory.description.call(), "Manage transfers using lock ups over time", "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.title.call(), + assert.equal(await I_LockUpVolumeRestrictionTMFactory.title.call(), "Lockup Volume Restriction Transfer Manager", "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.getInstructions.call(), + assert.equal(await I_LockUpVolumeRestrictionTMFactory.getInstructions.call(), "Allows an issuer to set lockup periods for user addresses, with funds distributed over time. Init function takes no parameters.", "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.version.call(), "1.0.0"); + assert.equal(await I_LockUpVolumeRestrictionTMFactory.version.call(), "1.0.0"); }); it("Should get the tags of the factory", async() => { - let tags = await I_VolumeRestrictionTransferManagerFactory.getTags.call(); + let tags = await I_LockUpVolumeRestrictionTMFactory.getTags.call(); assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Volume"); }); }); From b847f54cb7aab35860723407a41fecbc75641a5a Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 16 Nov 2018 12:30:39 +0530 Subject: [PATCH 03/15] minor fixes --- .../LockupVolumeRestrictionTM.sol | 177 ++++++++++++------ 1 file changed, 117 insertions(+), 60 deletions(-) diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol index 5c7183ee2..c9cdb3d8a 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol @@ -93,20 +93,13 @@ contract LockupVolumeRestrictionTM is ITransferManager { public withPerm(ADMIN) { - /*solium-disable-next-line security/no-block-members*/ - require(_startTime >= now, "start time is in past"); - _checkLockUpParams(_userAddress, _lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); - - lockUps[_userAddress].push(LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds)); - - emit AddNewLockUp( + _addLockUp( _userAddress, _lockupAmount, _startTime, _lockUpPeriodSeconds, - _releaseFrequencySeconds, - lockUps[_userAddress].length -1 - ); + _releaseFrequencySeconds + ); } /** @@ -136,7 +129,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { ); for (uint256 i = 0; i < _userAddresses.length; i++) { - addLockUp(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i]); + _addLockUp(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i]); } } @@ -147,21 +140,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { * @param _lockupIndex Index of the lockup need to be removed. */ function removeLockUp(address _userAddress, uint256 _lockupIndex) public withPerm(ADMIN) { - require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); - LockUp[] storage userLockup = lockUps[_userAddress]; - - emit RemoveLockUp( - _userAddress, - _lockupIndex - ); - - if (_lockupIndex != userLockup.length - 1) { - // move the last element in the array into the index that is desired to be removed. - userLockup[_lockupIndex] = userLockup[userLockup.length - 1]; - /*solium-disable-next-line security/no-block-members*/ - emit ChangeLockupIndex(_userAddress, userLockup.length - 1, _lockupIndex, now); - } - userLockup.length--; + _removeLockUp(_userAddress, _lockupIndex); } /** @@ -172,7 +151,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { function removeLockUpMulti(address[] _userAddresses, uint256[] _lockupIndexes) external withPerm(ADMIN) { require(_userAddresses.length == _lockupIndexes.length, "Array length mismatch"); for (uint256 i = 0; i < _userAddresses.length; i++) { - removeLockUp(_userAddresses[i], _lockupIndexes[i]); + _removeLockUp(_userAddresses[i], _lockupIndexes[i]); } } @@ -196,33 +175,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { public withPerm(ADMIN) { - require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); - - // Get the lockup from the master list and edit it - LockUp[] storage userLockup = lockUps[_userAddress]; - // If _startTime is equal to the previous startTime then it only allow to modify - // when there is no tokens gets unlocked from the lockup - if (_startTime == userLockup[_lockupIndex].startTime) { - require(_getUnlockedAmountForLockup(userLockup, _lockupIndex) == uint256(0)); - } else { - /*solium-disable-next-line security/no-block-members*/ - require(_startTime >= now, "start time is in past"); - } - _checkLockUpParams( - _userAddress, - _lockupAmount, - _lockUpPeriodSeconds, - _releaseFrequencySeconds - ); - - userLockup[_lockupIndex] = LockUp( - _lockupAmount, - _startTime, - _lockUpPeriodSeconds, - _releaseFrequencySeconds - ); - - emit ModifyLockUp( + _modifyLockUp( _userAddress, _lockupAmount, _startTime, @@ -258,7 +211,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { "Input array length mismatch" ); for (uint256 i = 0; i < _userAddresses.length; i++) { - modifyLockUp( + _modifyLockUp( _userAddresses[i], _lockupAmounts[i], _startTimes[i], @@ -301,11 +254,12 @@ contract LockupVolumeRestrictionTM is ITransferManager { } /** - * @notice Checks whether the transfer is allowed - * @param _userAddress Address of the user whose lock ups should be checked - * @param _amount Amount of tokens that need to transact + * @notice Use to get the total locked tokens for a given user + * @param _userAddress Address of the user + * @return uint256 Total locked tokens amount */ - function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + function getLockedTokenToUser(address _userAddress) public view returns(uint256) { + require(_userAddress != address(0), "Invalid address"); LockUp[] memory userLockup = lockUps[_userAddress]; uint256 totalRemainingLockedAmount = 0; for (uint256 i = 0; i < userLockup.length; i++) { @@ -314,10 +268,109 @@ contract LockupVolumeRestrictionTM is ITransferManager { // aggregating all the remaining locked amount for all the lockups for a given address totalRemainingLockedAmount = totalRemainingLockedAmount.add(remainingLockedAmount); } + return totalRemainingLockedAmount; + } + + function _modifyLockUp( + address _userAddress, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + uint256 _lockupIndex + ) + internal + { + require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); + + // Get the lockup from the master list and edit it + LockUp[] storage userLockup = lockUps[_userAddress]; + // If _startTime is equal to the previous startTime then it only allow to modify + // when there is no tokens gets unlocked from the lockup + if (_startTime == userLockup[_lockupIndex].startTime) { + require(_getUnlockedAmountForLockup(userLockup, _lockupIndex) == uint256(0)); + } else { + /*solium-disable-next-line security/no-block-members*/ + require(_startTime >= now, "start time is in past"); + } + _checkLockUpParams( + _userAddress, + _lockupAmount, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + + userLockup[_lockupIndex] = LockUp( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + + emit ModifyLockUp( + _userAddress, + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupIndex + ); + } + + function _removeLockUp(address _userAddress, uint256 _lockupIndex) internal { + require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); + LockUp[] storage userLockup = lockUps[_userAddress]; + + emit RemoveLockUp( + _userAddress, + _lockupIndex + ); + + if (_lockupIndex != userLockup.length - 1) { + // move the last element in the array into the index that is desired to be removed. + userLockup[_lockupIndex] = userLockup[userLockup.length - 1]; + /*solium-disable-next-line security/no-block-members*/ + emit ChangeLockupIndex(_userAddress, userLockup.length - 1, _lockupIndex, now); + } + userLockup.length--; + } + + function _addLockUp( + address _userAddress, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds + ) + internal + { + /*solium-disable-next-line security/no-block-members*/ + require(_startTime >= now, "start time is in past"); + _checkLockUpParams(_userAddress, _lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); + + lockUps[_userAddress].push(LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds)); + + emit AddNewLockUp( + _userAddress, + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + lockUps[_userAddress].length -1 + ); + } + + /** + * @notice Checks whether the transfer is allowed + * @param _userAddress Address of the user whose lock ups should be checked + * @param _amount Amount of tokens that need to transact + */ + function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); // Present balance of the user uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { - return Result.VALID; + return Result.NA; } return Result.INVALID; } @@ -380,6 +433,10 @@ contract LockupVolumeRestrictionTM is ITransferManager { // make sure the amount to be released per period is not too granular for the token uint256 totalPeriods = _lockUpPeriodSeconds.div(_releaseFrequencySeconds); uint256 amountPerPeriod = _lockupAmount.div(totalPeriods); + require( + amountPerPeriod.mul(totalPeriods) == _lockupAmount, + "lockup amount should be completely divisible by the amount per period" + ); require( amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, "The amount to be released per period is more granular than allowed by the token" From d28ef32a531b38f23417e59e249558d8a9a8fdb9 Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 16 Nov 2018 18:33:09 +0530 Subject: [PATCH 04/15] change to external --- .../TransferManager/LockupVolumeRestrictionTM.sol | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol index c9cdb3d8a..9753486ec 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol @@ -90,7 +90,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { uint256 _lockUpPeriodSeconds, uint256 _releaseFrequencySeconds ) - public + external withPerm(ADMIN) { _addLockUp( @@ -139,7 +139,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { * @param _userAddress Address of the user whose tokens are locked up * @param _lockupIndex Index of the lockup need to be removed. */ - function removeLockUp(address _userAddress, uint256 _lockupIndex) public withPerm(ADMIN) { + function removeLockUp(address _userAddress, uint256 _lockupIndex) external withPerm(ADMIN) { _removeLockUp(_userAddress, _lockupIndex); } @@ -172,7 +172,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { uint256 _releaseFrequencySeconds, uint256 _lockupIndex ) - public + external withPerm(ADMIN) { _modifyLockUp( @@ -201,7 +201,10 @@ contract LockupVolumeRestrictionTM is ITransferManager { uint256[] _lockUpPeriodsSeconds, uint256[] _releaseFrequenciesSeconds, uint256[] _lockupIndexes - ) public withPerm(ADMIN) { + ) + external + withPerm(ADMIN) + { require( _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ From d4d35e5def725926bf5667980b4c053bcbcdd2ed Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 16 Nov 2018 19:32:33 +0530 Subject: [PATCH 05/15] stack too deep erro fix --- .../Experimental/TransferManager/LockupVolumeRestrictionTM.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol index 9753486ec..887f78d3a 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol @@ -202,7 +202,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { uint256[] _releaseFrequenciesSeconds, uint256[] _lockupIndexes ) - external + public withPerm(ADMIN) { require( From 1f4e9c17846a4b6140147e30281a7ad909780178 Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 23 Nov 2018 00:00:08 +0530 Subject: [PATCH 06/15] remove some require statements --- ...ictionTM.sol => LockUpTransferManager.sol} | 91 ++++----- ...y.sol => LockUpTransferManagerFactory.sol} | 18 +- test/helpers/createInstances.js | 6 +- ...anager.js => w_lockup_transfer_manager.js} | 193 +++++++----------- 4 files changed, 130 insertions(+), 178 deletions(-) rename contracts/modules/Experimental/TransferManager/{LockupVolumeRestrictionTM.sol => LockUpTransferManager.sol} (96%) rename contracts/modules/Experimental/TransferManager/{LockupVolumeRestrictionTMFactory.sol => LockUpTransferManagerFactory.sol} (76%) rename test/{w_lockup_volume_restriction_transfer_manager.js => w_lockup_transfer_manager.js} (82%) diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol similarity index 96% rename from contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol rename to contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol index 887f78d3a..f1a8dddea 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol @@ -3,7 +3,7 @@ pragma solidity ^0.4.24; import "./../../TransferManager/ITransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; -contract LockupVolumeRestrictionTM is ITransferManager { +contract LockUpTransferManager is ITransferManager { using SafeMath for uint256; @@ -265,6 +265,7 @@ contract LockupVolumeRestrictionTM is ITransferManager { require(_userAddress != address(0), "Invalid address"); LockUp[] memory userLockup = lockUps[_userAddress]; uint256 totalRemainingLockedAmount = 0; + for (uint256 i = 0; i < userLockup.length; i++) { // Find out the remaining locked amount for a given lockup uint256 remainingLockedAmount = userLockup[i].lockupAmount.sub(_getUnlockedAmountForLockup(userLockup, i)); @@ -274,6 +275,45 @@ contract LockupVolumeRestrictionTM is ITransferManager { return totalRemainingLockedAmount; } + /** + * @notice Checks whether the transfer is allowed + * @param _userAddress Address of the user whose lock ups should be checked + * @param _amount Amount of tokens that need to transact + */ + function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); + // Present balance of the user + uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); + if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { + return Result.NA; + } + return Result.INVALID; + } + + /** + * @notice Provide the unlock amount for the given lockup for a particular user + */ + function _getUnlockedAmountForLockup(LockUp[] userLockup, uint256 _lockupIndex) internal view returns (uint256) { + /*solium-disable-next-line security/no-block-members*/ + if (userLockup[_lockupIndex].startTime > now) { + return 0; + } else if (userLockup[_lockupIndex].startTime.add(userLockup[_lockupIndex].lockUpPeriodSeconds) <= now) { + return userLockup[_lockupIndex].lockupAmount; + } else { + // Calculate the no. of periods for a lockup + uint256 noOfPeriods = (userLockup[_lockupIndex].lockUpPeriodSeconds).div(userLockup[_lockupIndex].releaseFrequencySeconds); + // Calculate the transaction time lies in which period + /*solium-disable-next-line security/no-block-members*/ + uint256 elapsedPeriod = (now.sub(userLockup[_lockupIndex].startTime)).div(userLockup[_lockupIndex].releaseFrequencySeconds); + // Calculate the allowed unlocked amount per period + uint256 amountPerPeriod = (userLockup[_lockupIndex].lockupAmount).div(noOfPeriods); + // Find out the unlocked amount for a given lockup + uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); + return unLockedAmount; + } + + } + function _modifyLockUp( address _userAddress, uint256 _lockupAmount, @@ -363,43 +403,6 @@ contract LockupVolumeRestrictionTM is ITransferManager { ); } - /** - * @notice Checks whether the transfer is allowed - * @param _userAddress Address of the user whose lock ups should be checked - * @param _amount Amount of tokens that need to transact - */ - function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { - uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); - // Present balance of the user - uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); - if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { - return Result.NA; - } - return Result.INVALID; - } - - /** - * @notice Provide the unlock amount for the given lockup for a particular user - */ - function _getUnlockedAmountForLockup(LockUp[] userLockup, uint256 _lockupIndex) internal view returns (uint256) { - /*solium-disable-next-line security/no-block-members*/ - if (userLockup[_lockupIndex].startTime > now) { - return 0; - } else { - // Calculate the no. of periods for a lockup - uint256 noOfPeriods = (userLockup[_lockupIndex].lockUpPeriodSeconds).div(userLockup[_lockupIndex].releaseFrequencySeconds); - // Calculate the transaction time lies in which period - /*solium-disable-next-line security/no-block-members*/ - uint256 elapsedPeriod = (now.sub(userLockup[_lockupIndex].startTime)).div(userLockup[_lockupIndex].releaseFrequencySeconds); - // Calculate the allowed unlocked amount per period - uint256 amountPerPeriod = (userLockup[_lockupIndex].lockupAmount).div(noOfPeriods); - // Find out the unlocked amount for a given lockup - uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); - return unLockedAmount; - } - - } - /** * @notice Parameter checking function for creating or editing a lockup. This function will cause an exception if any of the parameters are bad. * @param _userAddress Address whom lockup is being applied @@ -432,18 +435,6 @@ contract LockupVolumeRestrictionTM is ITransferManager { _lockUpPeriodSeconds % _releaseFrequencySeconds == 0, "lockUpPeriodSeconds must be evenly divisible by releaseFrequencySeconds" ); - - // make sure the amount to be released per period is not too granular for the token - uint256 totalPeriods = _lockUpPeriodSeconds.div(_releaseFrequencySeconds); - uint256 amountPerPeriod = _lockupAmount.div(totalPeriods); - require( - amountPerPeriod.mul(totalPeriods) == _lockupAmount, - "lockup amount should be completely divisible by the amount per period" - ); - require( - amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, - "The amount to be released per period is more granular than allowed by the token" - ); } /** diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol similarity index 76% rename from contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol rename to contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol index 5be77b6e0..2a79e2528 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol +++ b/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol @@ -1,12 +1,12 @@ pragma solidity ^0.4.24; import "./../../ModuleFactory.sol"; -import "./LockupVolumeRestrictionTM.sol"; +import "./LockUpTransferManager.sol"; /** - * @title Factory for deploying ManualApprovalTransferManager module + * @title Factory for deploying LockUpTransferManager module */ -contract LockupVolumeRestrictionTMFactory is ModuleFactory { +contract LockUpTransferManagerFactory is ModuleFactory { /** * @notice Constructor @@ -19,8 +19,8 @@ contract LockupVolumeRestrictionTMFactory is ModuleFactory { ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { version = "1.0.0"; - name = "LockupVolumeRestrictionTM"; - title = "Lockup Volume Restriction Transfer Manager"; + name = "LockUpTransferManager"; + title = "LockUp Transfer Manager"; description = "Manage transfers using lock ups over time"; compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); @@ -33,10 +33,10 @@ contract LockupVolumeRestrictionTMFactory is ModuleFactory { function deploy(bytes /* _data */) external returns(address) { if (setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided"); - LockupVolumeRestrictionTM lockupVolumeRestrictionTransferManager = new LockupVolumeRestrictionTM(msg.sender, address(polyToken)); + LockUpTransferManager lockUpTransferManager = new LockUpTransferManager(msg.sender, address(polyToken)); /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(lockupVolumeRestrictionTransferManager), getName(), address(this), msg.sender, now); - return address(lockupVolumeRestrictionTransferManager); + emit GenerateModuleFromFactory(address(lockUpTransferManager), getName(), address(this), msg.sender, now); + return address(lockUpTransferManager); } /** @@ -61,7 +61,7 @@ contract LockupVolumeRestrictionTMFactory is ModuleFactory { */ function getTags() external view returns(bytes32[]) { bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Volume"; + availableTags[0] = "LockUp"; availableTags[1] = "Transfer Restriction"; return availableTags; } diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index 2021d350e..7c0f42487 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -23,7 +23,7 @@ const STFactory = artifacts.require("./STFactory.sol"); const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); const CountTransferManagerFactory = artifacts.require("./CountTransferManagerFactory.sol"); -const VolumeRestrictionTransferManagerFactory = artifacts.require("./LockupVolumeRestrictionTMFactory"); +const LockUpTransferManagerFactory = artifacts.require("./LockUpTransferManagerFactory"); const PreSaleSTOFactory = artifacts.require("./PreSaleSTOFactory.sol"); const PolyToken = artifacts.require("./PolyToken.sol"); const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); @@ -244,11 +244,11 @@ export async function deployPercentageTMAndVerified(accountPolymath, MRProxyInst } export async function deployLockupVolumeRTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_VolumeRestrictionTransferManagerFactory = await VolumeRestrictionTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); + I_VolumeRestrictionTransferManagerFactory = await LockUpTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); assert.notEqual( I_VolumeRestrictionTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", - "VolumeRestrictionTransferManagerFactory contract was not deployed" + "LockUpTransferManagerFactory contract was not deployed" ); await registerAndVerifyByMR(I_VolumeRestrictionTransferManagerFactory.address, accountPolymath, MRProxyInstance); diff --git a/test/w_lockup_volume_restriction_transfer_manager.js b/test/w_lockup_transfer_manager.js similarity index 82% rename from test/w_lockup_volume_restriction_transfer_manager.js rename to test/w_lockup_transfer_manager.js index 2e0db1844..86031e94b 100644 --- a/test/w_lockup_volume_restriction_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -7,14 +7,14 @@ import { catchRevert } from "./helpers/exceptions"; const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const LockUpVolumeRestrictionTM = artifacts.require('./LockupVolumeRestrictionTM'); +const LockUpTransferManager = artifacts.require('./LockUpTransferManager'); const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); const Web3 = require('web3'); const BigNumber = require('bignumber.js'); const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port -contract('LockupVolumeRestrictionTransferManager', accounts => { +contract('LockUpTransferManager', accounts => { // Accounts Variable declaration let account_polymath; @@ -33,13 +33,13 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { let message = "Transaction Should Fail!"; // Contract Instance Declaration - let P_LockUpVolumeRestrictionTMFactory; + let P_LockUpTransferManagerFactory; let I_SecurityTokenRegistryProxy; - let P_LockUpVolumeRestrictionTM; + let P_LockUpTransferManager; let I_GeneralTransferManagerFactory; - let I_LockUpVolumeRestrictionTMFactory; + let I_LockUpTransferManagerFactory; let I_GeneralPermissionManager; - let I_LockUpVolumeRestrictionTM; + let I_LockUpTransferManager; let I_GeneralTransferManager; let I_ModuleRegistryProxy; let I_ModuleRegistry; @@ -103,9 +103,9 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ] = instances; // STEP 4(c): Deploy the LockUpVolumeRestrictionTMFactory - [I_LockUpVolumeRestrictionTMFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); // STEP 4(d): Deploy the LockUpVolumeRestrictionTMFactory - [P_LockUpVolumeRestrictionTMFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + [P_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); // Printing all the contract addresses console.log(` @@ -121,7 +121,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} LockupVolumeRestrictionTransferManagerFactory: - ${I_LockUpVolumeRestrictionTMFactory.address} + ${I_LockUpTransferManagerFactory.address} ----------------------------------------------------------------------------- `); }); @@ -278,50 +278,50 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ); }); - it("Should unsuccessfully attach the LockUpVolumeRestrictionTM factory with the security token -- failed because Token is not paid", async () => { + it("Should unsuccessfully attach the LockUpTransferManager factory with the security token -- failed because Token is not paid", async () => { await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); await catchRevert( - I_SecurityToken.addModule(P_LockUpVolumeRestrictionTMFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) ) }); - it("Should successfully attach the LockUpVolumeRestrictionTM factory with the security token", async () => { + it("Should successfully attach the LockUpTransferManager factory with the security token", async () => { let snapId = await takeSnapshot(); await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); - const tx = await I_SecurityToken.addModule(P_LockUpVolumeRestrictionTMFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[3].args._name) .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "LockUpVolumeRestrictionTM module was not added" + "LockUpTransferManager", + "LockUpTransferManager module was not added" ); - P_LockUpVolumeRestrictionTM = LockUpVolumeRestrictionTM.at(tx.logs[3].args._module); + P_LockUpTransferManager = LockUpTransferManager.at(tx.logs[3].args._module); await revertToSnapshot(snapId); }); it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the non-divisible security token", async () => { - const tx = await I_SecurityToken.addModule(I_LockUpVolumeRestrictionTMFactory.address, 0, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken.addModule(I_LockUpTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name) .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "LockUpVolumeRestrictionTM module was not added" + "LockUpTransferManager", + "LockUpTransferManager module was not added" ); - I_LockUpVolumeRestrictionTM = LockUpVolumeRestrictionTM.at(tx.logs[2].args._module); + I_LockUpTransferManager = LockUpTransferManager.at(tx.logs[2].args._module); }); it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the divisible security token", async () => { - const tx = await I_SecurityToken_div.addModule(I_LockUpVolumeRestrictionTMFactory.address, 0, 0, 0, { from: token_owner }); + const tx = await I_SecurityToken_div.addModule(I_LockUpTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name) .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "LockUpVolumeRestrictionTM module was not added" + "LockUpTransferManager", + "LockUpTransferManager module was not added" ); - I_LockUpVolumeRestrictionTM_div = LockUpVolumeRestrictionTM.at(tx.logs[2].args._module); + I_LockUpVolumeRestrictionTM_div = LockUpTransferManager.at(tx.logs[2].args._module); }); it("Add a new token holder", async() => { @@ -349,7 +349,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }); it("Should pause the tranfers at transferManager level", async() => { - let tx = await I_LockUpVolumeRestrictionTM.pause({from: token_owner}); + let tx = await I_LockUpTransferManager.pause({from: token_owner}); }); it("Should still be able to transfer between existing token holders up to limit", async() => { @@ -363,14 +363,14 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }); it("Should unpause the tranfers at transferManager level", async() => { - await I_LockUpVolumeRestrictionTM.unpause({from: token_owner}); + await I_LockUpTransferManager.unpause({from: token_owner}); }); it("Should prevent the creation of a lockup with bad parameters where the lockupAmount is zero", async() => { // create a lockup // this will generate an exception because the lockupAmount is zero await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( + I_LockUpTransferManager.addLockUp( account_investor2, 0, latestTime() + + duration.seconds(1), @@ -387,7 +387,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup // this will generate an exception because the releaseFrequencySeconds is zero await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( + I_LockUpTransferManager.addLockUp( account_investor2, web3.utils.toWei('1', 'ether'), latestTime() + duration.seconds(1), @@ -404,7 +404,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup // this will generate an exception because the lockUpPeriodSeconds is zero await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( + I_LockUpTransferManager.addLockUp( account_investor2, web3.utils.toWei('1', 'ether'), latestTime() + duration.seconds(1), @@ -422,7 +422,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup // this will generate an exception because we're locking up 5e17 tokens but the granularity is 5e18 tokens await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( + I_LockUpTransferManager.addLockUp( account_investor2, web3.utils.toWei('0.5', 'ether'), latestTime() + + duration.seconds(1), @@ -477,7 +477,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // over 17 seconds total, with 4 periods. // this will generate an exception because 17 is not evenly divisble by 4. await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( + I_LockUpTransferManager.addLockUp( account_investor2, web3.utils.toWei('4', 'ether'), latestTime(), @@ -490,53 +490,13 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ); }); - it("Should prevent the creation of a lockup with bad parameters where the total amount being locked up isn't evenly divisible by the number of total periods", async() => { - - // create a lockup for a balance of 1 eth - await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( - account_investor2, - web3.utils.toWei('1', 'ether'), - latestTime(), - duration.seconds(400000), - duration.seconds(100000), - { - from: token_owner - } - ) - ); - }); - - it("Should prevent the creation of a lockup with bad parameters where the amount to be released per period is too granular for the token", async() => { - - // balance should be 9000000000000000000 here (9 eth) - let balance = await I_SecurityToken.balanceOf(account_investor2) - - // create a lockup for their entire balance - // over 16 seconds total, with 4 periods of 4 seconds each. - // this will generate an exception because 9000000000000000000 / 4 = 2250000000000000000 but the token granularity is 1000000000000000000 - await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUp( - account_investor2, - balance, - latestTime() + duration.seconds(1), - 16, - 4, - { - from: token_owner - } - ) - ); - - }); - it("Should prevent the transfer of tokens in a lockup", async() => { let balance = await I_SecurityToken.balanceOf(account_investor2) console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // create a lockup for their entire balance - // over 12 seconds total, with 3 periods of 4 seconds each. - await I_LockUpVolumeRestrictionTM.addLockUp( + // over 12 seconds total, with 3 periods of 20 seconds each. + await I_LockUpTransferManager.addLockUp( account_investor2, balance, latestTime() + duration.seconds(1), @@ -547,7 +507,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { } ); await increaseTime(2); - let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) @@ -557,7 +517,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { // wait 20 seconds await increaseTime(duration.seconds(20)); - let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) @@ -565,13 +525,13 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }); it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); it("Should again transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); @@ -580,7 +540,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // wait 20 more seconds + 1 to get rid of same block time await increaseTime(duration.seconds(21)); - let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); }); @@ -609,8 +569,8 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { let balance = await I_SecurityToken.balanceOf(account_investor2) // wait 20 more seconds + 1 to get rid of same block time - await increaseTime(duration.seconds(20)); - + await increaseTime(duration.seconds(21)); + console.log((await I_LockUpTransferManager.getLockedTokenToUser.call(account_investor2)).toNumber()); await I_SecurityToken.transfer(account_investor1, balance, { from: account_investor2 }); assert.equal( (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), @@ -620,7 +580,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUpMulti( + I_LockUpTransferManager.addLockUpMulti( [account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], @@ -635,7 +595,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUpMulti( + I_LockUpTransferManager.addLockUpMulti( [account_investor3, account_investor3], [], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], @@ -650,7 +610,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUpMulti( + I_LockUpTransferManager.addLockUpMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], @@ -665,7 +625,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.addLockUpMulti( + I_LockUpTransferManager.addLockUpMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], @@ -679,7 +639,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }) it("Should add the multiple lockup to a address", async() => { - await I_LockUpVolumeRestrictionTM.addLockUpMulti( + await I_LockUpTransferManager.addLockUpMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], @@ -691,8 +651,8 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ); await increaseTime(1); - let tx = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor3, 0); - let tx2 = await I_LockUpVolumeRestrictionTM.getLockUp.call(account_investor3, 1); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor3, 0); + let tx2 = await I_LockUpTransferManager.getLockUp.call(account_investor3, 1); console.log("Total Amount get unlocked:", (tx[4].toNumber()) + (tx2[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor3 }) @@ -733,7 +693,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should remove multiple lockup --failed because of bad owner", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.removeLockUpMulti( + I_LockUpTransferManager.removeLockUpMulti( [account_investor3, account_investor3], [0,1], { @@ -745,7 +705,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should remove the multiple lockup -- failed because of invalid index", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.removeLockUpMulti( + I_LockUpTransferManager.removeLockUpMulti( [account_investor3, account_investor3], [0,3], { @@ -756,7 +716,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { }) it("Should remove the multiple lockup", async() => { - await I_LockUpVolumeRestrictionTM.removeLockUpMulti( + await I_LockUpTransferManager.removeLockUpMulti( [account_investor3, account_investor3], [1,0], { @@ -770,7 +730,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should fail to modify the lockup -- because of bad owner", async() => { await I_SecurityToken.mint(account_investor3, web3.utils.toWei("9"), {from: token_owner}); - let tx = await I_LockUpVolumeRestrictionTM.addLockUp( + let tx = await I_LockUpTransferManager.addLockUp( account_investor3, web3.utils.toWei("9"), latestTime() + duration.minutes(5), @@ -783,7 +743,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { temp = (tx.logs[0].args.lockupIndex).toNumber(); await catchRevert( // edit the lockup - I_LockUpVolumeRestrictionTM.modifyLockUp( + I_LockUpTransferManager.modifyLockUp( account_investor3, web3.utils.toWei("9"), latestTime() + duration.seconds(1), @@ -800,7 +760,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Modify the lockup when startTime is in past -- failed because startTime is in the past", async() => { await catchRevert( // edit the lockup - I_LockUpVolumeRestrictionTM.modifyLockUp( + I_LockUpTransferManager.modifyLockUp( account_investor3, web3.utils.toWei("9"), latestTime() - duration.seconds(50), @@ -817,7 +777,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Modify the lockup when startTime is in past -- failed because of invalid index", async() => { await catchRevert( // edit the lockup - I_LockUpVolumeRestrictionTM.modifyLockUp( + I_LockUpTransferManager.modifyLockUp( account_investor3, web3.utils.toWei("9"), latestTime() + duration.seconds(50), @@ -833,7 +793,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("should successfully modify the lockup", async() => { // edit the lockup - await I_LockUpVolumeRestrictionTM.modifyLockUp( + await I_LockUpTransferManager.modifyLockUp( account_investor3, web3.utils.toWei("9"), latestTime() + duration.seconds(50), @@ -855,7 +815,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // create a lockup for their entire balance // over 16 seconds total, with 4 periods of 4 seconds each. - await I_LockUpVolumeRestrictionTM.addLockUp( + await I_LockUpTransferManager.addLockUp( account_investor1, balance, latestTime() + duration.minutes(5), @@ -871,10 +831,11 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { ); // check and get the lockup - let lockUpCount = await I_LockUpVolumeRestrictionTM.getLockUpsLength(account_investor1); + let lockUpCount = await I_LockUpTransferManager.getLockUpsLength(account_investor1); assert.equal(lockUpCount, 1) - let lockUp = await I_LockUpVolumeRestrictionTM.getLockUp(account_investor1, 0); + let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, 0); + console.log(lockUp); // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount assert.equal( lockUp[0].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber(), @@ -886,7 +847,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { // edit the lockup temp = latestTime() + duration.seconds(1); - await I_LockUpVolumeRestrictionTM.modifyLockUp( + await I_LockUpTransferManager.modifyLockUp( account_investor1, balance, temp, @@ -913,11 +874,11 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Modify the lockup during the lockup periods --fail because already tokens get unlocked", async() => { let balance = await I_SecurityToken.balanceOf(account_investor1) - let lockUp = await I_LockUpVolumeRestrictionTM.getLockUp(account_investor1, 0); + let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, 0); console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // edit the lockup await catchRevert( - I_LockUpVolumeRestrictionTM.modifyLockUp( + I_LockUpTransferManager.modifyLockUp( account_investor1, balance, temp, @@ -933,48 +894,48 @@ contract('LockupVolumeRestrictionTransferManager', accounts => { it("Should succesfully get the lockup - fail because array index out of bound", async() => { await catchRevert( - I_LockUpVolumeRestrictionTM.getLockUp(account_investor1, 9) + I_LockUpTransferManager.getLockUp(account_investor1, 9) ); }) it("Should get configuration function signature", async() => { - let sig = await I_LockUpVolumeRestrictionTM.getInitFunction.call(); + let sig = await I_LockUpTransferManager.getInitFunction.call(); assert.equal(web3.utils.hexToNumber(sig), 0); }); it("Should get the permission", async() => { - let perm = await I_LockUpVolumeRestrictionTM.getPermissions.call(); + let perm = await I_LockUpTransferManager.getPermissions.call(); assert.equal(perm.length, 1); assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ''), "ADMIN") }); }); - describe("LockUpVolumeRestrictionTM Transfer Manager Factory test cases", async() => { + describe("LockUpTransferManager Transfer Manager Factory test cases", async() => { it("Should get the exact details of the factory", async() => { - assert.equal(await I_LockUpVolumeRestrictionTMFactory.getSetupCost.call(),0); - assert.equal((await I_LockUpVolumeRestrictionTMFactory.getTypes.call())[0],2); - assert.equal(web3.utils.toAscii(await I_LockUpVolumeRestrictionTMFactory.getName.call()) + assert.equal(await I_LockUpTransferManagerFactory.getSetupCost.call(),0); + assert.equal((await I_LockUpTransferManagerFactory.getTypes.call())[0],2); + assert.equal(web3.utils.toAscii(await I_LockUpTransferManagerFactory.getName.call()) .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", + "LockUpTransferManager", "Wrong Module added"); - assert.equal(await I_LockUpVolumeRestrictionTMFactory.description.call(), + assert.equal(await I_LockUpTransferManagerFactory.description.call(), "Manage transfers using lock ups over time", "Wrong Module added"); - assert.equal(await I_LockUpVolumeRestrictionTMFactory.title.call(), - "Lockup Volume Restriction Transfer Manager", + assert.equal(await I_LockUpTransferManagerFactory.title.call(), + "LockUp Transfer Manager", "Wrong Module added"); - assert.equal(await I_LockUpVolumeRestrictionTMFactory.getInstructions.call(), + assert.equal(await I_LockUpTransferManagerFactory.getInstructions.call(), "Allows an issuer to set lockup periods for user addresses, with funds distributed over time. Init function takes no parameters.", "Wrong Module added"); - assert.equal(await I_LockUpVolumeRestrictionTMFactory.version.call(), "1.0.0"); + assert.equal(await I_LockUpTransferManagerFactory.version.call(), "1.0.0"); }); it("Should get the tags of the factory", async() => { - let tags = await I_LockUpVolumeRestrictionTMFactory.getTags.call(); - assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Volume"); + let tags = await I_LockUpTransferManagerFactory.getTags.call(); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "LockUp"); }); }); From 4eb3a5a160768304d91dcadc3ae1f20fdfe2b166 Mon Sep 17 00:00:00 2001 From: satyam Date: Thu, 29 Nov 2018 18:47:10 +0530 Subject: [PATCH 07/15] redesign the lockup module --- .../TransferManager/LockUpTransferManager.sol | 455 ------------ .../TransferManager/LockUpTransferManager.sol | 651 ++++++++++++++++++ .../LockUpTransferManagerFactory.sol | 2 +- 3 files changed, 652 insertions(+), 456 deletions(-) delete mode 100644 contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol create mode 100644 contracts/modules/TransferManager/LockUpTransferManager.sol rename contracts/modules/{Experimental => }/TransferManager/LockUpTransferManagerFactory.sol (98%) diff --git a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol deleted file mode 100644 index f1a8dddea..000000000 --- a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol +++ /dev/null @@ -1,455 +0,0 @@ -pragma solidity ^0.4.24; - -import "./../../TransferManager/ITransferManager.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; - -contract LockUpTransferManager is ITransferManager { - - using SafeMath for uint256; - - // permission definition - bytes32 public constant ADMIN = "ADMIN"; - - // a per-user lockup - struct LockUp { - uint256 lockupAmount; // Amount to be locked - uint256 startTime; // when this lockup starts (seconds) - uint256 lockUpPeriodSeconds; // total period of lockup (seconds) - uint256 releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) - } - - // maps user addresses to an array of lockups for that user - mapping (address => LockUp[]) internal lockUps; - - event AddNewLockUp( - address indexed userAddress, - uint256 lockupAmount, - uint256 startTime, - uint256 lockUpPeriodSeconds, - uint256 releaseFrequencySeconds, - uint256 indexed lockupIndex - ); - - event RemoveLockUp( - address indexed userAddress, - uint256 indexed lockupIndex - ); - - event ModifyLockUp( - address indexed userAddress, - uint256 lockupAmount, - uint256 startTime, - uint256 lockUpPeriodSeconds, - uint256 releaseFrequencySeconds, - uint256 indexed lockupIndex - ); - - event ChangeLockupIndex( - address indexed _userAddress, - uint256 indexed _oldLockupIndex, - uint256 indexed _newLockupIndex, - uint256 _timestamp - ); - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - */ - constructor (address _securityToken, address _polyAddress) - public - Module(_securityToken, _polyAddress) - { - } - - /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred - * @param _from Address of the sender - * @param _amount The amount of tokens to transfer - */ - function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool /*_isTransfer*/) public returns(Result) { - // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user - if (!paused && _from != address(0) && lockUps[_from].length != 0) { - // check if this transfer is valid - return _checkIfValidTransfer(_from, _amount); - } - return Result.NA; - } - - /** - * @notice Lets the admin create a volume restriction lockup for a given address. - * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockupAmount Amount of tokens that need to lock. - * @param _startTime When this lockup starts (seconds) - * @param _lockUpPeriodSeconds Total period of lockup (seconds) - * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - */ - function addLockUp( - address _userAddress, - uint256 _lockupAmount, - uint256 _startTime, - uint256 _lockUpPeriodSeconds, - uint256 _releaseFrequencySeconds - ) - external - withPerm(ADMIN) - { - _addLockUp( - _userAddress, - _lockupAmount, - _startTime, - _lockUpPeriodSeconds, - _releaseFrequencySeconds - ); - } - - /** - * @notice Lets the admin create multiple volume restriction lockups for multiple given addresses. - * @param _userAddresses Array of address of the user whose tokens should be locked up - * @param _lockupAmounts Array of the amounts that need to be locked for the different addresses. - * @param _startTimes Array of When this lockup starts (seconds) - * @param _lockUpPeriodsSeconds Array of total periods of lockup (seconds) - * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) - */ - function addLockUpMulti( - address[] _userAddresses, - uint256[] _lockupAmounts, - uint256[] _startTimes, - uint256[] _lockUpPeriodsSeconds, - uint256[] _releaseFrequenciesSeconds - ) - external - withPerm(ADMIN) - { - require( - _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _lockupAmounts.length, - "Input array length mismatch" - ); - - for (uint256 i = 0; i < _userAddresses.length; i++) { - _addLockUp(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i]); - } - - } - - /** - * @notice Lets the admin remove a user's lock up - * @param _userAddress Address of the user whose tokens are locked up - * @param _lockupIndex Index of the lockup need to be removed. - */ - function removeLockUp(address _userAddress, uint256 _lockupIndex) external withPerm(ADMIN) { - _removeLockUp(_userAddress, _lockupIndex); - } - - /** - * @notice Use to remove the lockup for multiple users - * @param _userAddresses Array of addresses of the user whose tokens are locked up - * @param _lockupIndexes Array of the indexes to the lockup that needs to be removed. - */ - function removeLockUpMulti(address[] _userAddresses, uint256[] _lockupIndexes) external withPerm(ADMIN) { - require(_userAddresses.length == _lockupIndexes.length, "Array length mismatch"); - for (uint256 i = 0; i < _userAddresses.length; i++) { - _removeLockUp(_userAddresses[i], _lockupIndexes[i]); - } - } - - /** - * @notice Lets the admin modify a volume restriction lockup for a given address. - * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockupAmount Amount of tokens that needs to be locked - * @param _startTime When this lockup starts (seconds) - * @param _lockUpPeriodSeconds Total period of lockup (seconds) - * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param _lockupIndex Index of the lockup that needs to be modified. - */ - function modifyLockUp( - address _userAddress, - uint256 _lockupAmount, - uint256 _startTime, - uint256 _lockUpPeriodSeconds, - uint256 _releaseFrequencySeconds, - uint256 _lockupIndex - ) - external - withPerm(ADMIN) - { - _modifyLockUp( - _userAddress, - _lockupAmount, - _startTime, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - _lockupIndex - ); - } - - /** - * @notice Lets the admin modify a volume restriction lockup for a multiple address. - * @param _userAddresses Array of address of the user whose tokens should be locked up - * @param _lockupAmounts Array of the amount of tokens that needs to be locked for the respective addresses. - * @param _startTimes Array of the start time of the lockups (seconds) - * @param _lockUpPeriodsSeconds Array of unix timestamp for the list of lockups (seconds). - * @param _releaseFrequenciesSeconds How often to release a tranche of tokens (seconds) - * @param _lockupIndexes Array of the lockup indexes that needs to be modified - */ - function modifyLockUpMulti( - address[] _userAddresses, - uint256[] _lockupAmounts, - uint256[] _startTimes, - uint256[] _lockUpPeriodsSeconds, - uint256[] _releaseFrequenciesSeconds, - uint256[] _lockupIndexes - ) - public - withPerm(ADMIN) - { - require( - _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _lockupAmounts.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _lockupIndexes.length, - "Input array length mismatch" - ); - for (uint256 i = 0; i < _userAddresses.length; i++) { - _modifyLockUp( - _userAddresses[i], - _lockupAmounts[i], - _startTimes[i], - _lockUpPeriodsSeconds[i], - _releaseFrequenciesSeconds[i], - _lockupIndexes[i] - ); - } - } - - /** - * @notice Get the length of the lockups array for a specific user address - * @param _userAddress Address of the user whose tokens should be locked up - */ - function getLockUpsLength(address _userAddress) public view returns (uint256) { - return lockUps[_userAddress].length; - } - - /** - * @notice Get a specific element in a user's lockups array given the user's address and the element index - * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockUpIndex The index of the LockUp to edit for the given userAddress - */ - function getLockUp(address _userAddress, uint256 _lockUpIndex) public view returns ( - uint256 lockupAmount, - uint256 startTime, - uint256 lockUpPeriodSeconds, - uint256 releaseFrequencySeconds, - uint256 unlockedAmount - ) { - require(lockUps[_userAddress].length > _lockUpIndex, "Invalid index"); - LockUp[] memory userLockup = lockUps[_userAddress]; - return ( - userLockup[_lockUpIndex].lockupAmount, - userLockup[_lockUpIndex].startTime, - userLockup[_lockUpIndex].lockUpPeriodSeconds, - userLockup[_lockUpIndex].releaseFrequencySeconds, - _getUnlockedAmountForLockup(userLockup, _lockUpIndex) - ); - } - - /** - * @notice Use to get the total locked tokens for a given user - * @param _userAddress Address of the user - * @return uint256 Total locked tokens amount - */ - function getLockedTokenToUser(address _userAddress) public view returns(uint256) { - require(_userAddress != address(0), "Invalid address"); - LockUp[] memory userLockup = lockUps[_userAddress]; - uint256 totalRemainingLockedAmount = 0; - - for (uint256 i = 0; i < userLockup.length; i++) { - // Find out the remaining locked amount for a given lockup - uint256 remainingLockedAmount = userLockup[i].lockupAmount.sub(_getUnlockedAmountForLockup(userLockup, i)); - // aggregating all the remaining locked amount for all the lockups for a given address - totalRemainingLockedAmount = totalRemainingLockedAmount.add(remainingLockedAmount); - } - return totalRemainingLockedAmount; - } - - /** - * @notice Checks whether the transfer is allowed - * @param _userAddress Address of the user whose lock ups should be checked - * @param _amount Amount of tokens that need to transact - */ - function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { - uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); - // Present balance of the user - uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); - if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { - return Result.NA; - } - return Result.INVALID; - } - - /** - * @notice Provide the unlock amount for the given lockup for a particular user - */ - function _getUnlockedAmountForLockup(LockUp[] userLockup, uint256 _lockupIndex) internal view returns (uint256) { - /*solium-disable-next-line security/no-block-members*/ - if (userLockup[_lockupIndex].startTime > now) { - return 0; - } else if (userLockup[_lockupIndex].startTime.add(userLockup[_lockupIndex].lockUpPeriodSeconds) <= now) { - return userLockup[_lockupIndex].lockupAmount; - } else { - // Calculate the no. of periods for a lockup - uint256 noOfPeriods = (userLockup[_lockupIndex].lockUpPeriodSeconds).div(userLockup[_lockupIndex].releaseFrequencySeconds); - // Calculate the transaction time lies in which period - /*solium-disable-next-line security/no-block-members*/ - uint256 elapsedPeriod = (now.sub(userLockup[_lockupIndex].startTime)).div(userLockup[_lockupIndex].releaseFrequencySeconds); - // Calculate the allowed unlocked amount per period - uint256 amountPerPeriod = (userLockup[_lockupIndex].lockupAmount).div(noOfPeriods); - // Find out the unlocked amount for a given lockup - uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); - return unLockedAmount; - } - - } - - function _modifyLockUp( - address _userAddress, - uint256 _lockupAmount, - uint256 _startTime, - uint256 _lockUpPeriodSeconds, - uint256 _releaseFrequencySeconds, - uint256 _lockupIndex - ) - internal - { - require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); - - // Get the lockup from the master list and edit it - LockUp[] storage userLockup = lockUps[_userAddress]; - // If _startTime is equal to the previous startTime then it only allow to modify - // when there is no tokens gets unlocked from the lockup - if (_startTime == userLockup[_lockupIndex].startTime) { - require(_getUnlockedAmountForLockup(userLockup, _lockupIndex) == uint256(0)); - } else { - /*solium-disable-next-line security/no-block-members*/ - require(_startTime >= now, "start time is in past"); - } - _checkLockUpParams( - _userAddress, - _lockupAmount, - _lockUpPeriodSeconds, - _releaseFrequencySeconds - ); - - userLockup[_lockupIndex] = LockUp( - _lockupAmount, - _startTime, - _lockUpPeriodSeconds, - _releaseFrequencySeconds - ); - - emit ModifyLockUp( - _userAddress, - _lockupAmount, - _startTime, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - _lockupIndex - ); - } - - function _removeLockUp(address _userAddress, uint256 _lockupIndex) internal { - require(lockUps[_userAddress].length > _lockupIndex, "Invalid index"); - LockUp[] storage userLockup = lockUps[_userAddress]; - - emit RemoveLockUp( - _userAddress, - _lockupIndex - ); - - if (_lockupIndex != userLockup.length - 1) { - // move the last element in the array into the index that is desired to be removed. - userLockup[_lockupIndex] = userLockup[userLockup.length - 1]; - /*solium-disable-next-line security/no-block-members*/ - emit ChangeLockupIndex(_userAddress, userLockup.length - 1, _lockupIndex, now); - } - userLockup.length--; - } - - function _addLockUp( - address _userAddress, - uint256 _lockupAmount, - uint256 _startTime, - uint256 _lockUpPeriodSeconds, - uint256 _releaseFrequencySeconds - ) - internal - { - /*solium-disable-next-line security/no-block-members*/ - require(_startTime >= now, "start time is in past"); - _checkLockUpParams(_userAddress, _lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); - - lockUps[_userAddress].push(LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds)); - - emit AddNewLockUp( - _userAddress, - _lockupAmount, - _startTime, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - lockUps[_userAddress].length -1 - ); - } - - /** - * @notice Parameter checking function for creating or editing a lockup. This function will cause an exception if any of the parameters are bad. - * @param _userAddress Address whom lockup is being applied - * @param _lockupAmount Amount that needs to be locked - * @param _lockUpPeriodSeconds Total period of lockup (seconds) - * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - */ - function _checkLockUpParams( - address _userAddress, - uint256 _lockupAmount, - uint256 _lockUpPeriodSeconds, - uint256 _releaseFrequencySeconds - ) - internal - view - { - require(_userAddress != address(0), "Invalid address"); - require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); - require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); - require(_lockupAmount != 0, "lockupAmount cannot be zero"); - - // check that the total amount to be released isn't too granular - require( - _lockupAmount % ISecurityToken(securityToken).granularity() == 0, - "The total amount to be released is more granular than allowed by the token" - ); - - // check that releaseFrequencySeconds evenly divides lockUpPeriodSeconds - require( - _lockUpPeriodSeconds % _releaseFrequencySeconds == 0, - "lockUpPeriodSeconds must be evenly divisible by releaseFrequencySeconds" - ); - } - - /** - * @notice This function returns the signature of configure function - */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(0); - } - - /** - * @notice Returns the permissions flag that are associated with Percentage transfer Manager - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = ADMIN; - return allPermissions; - } -} diff --git a/contracts/modules/TransferManager/LockUpTransferManager.sol b/contracts/modules/TransferManager/LockUpTransferManager.sol new file mode 100644 index 000000000..b2b940b3d --- /dev/null +++ b/contracts/modules/TransferManager/LockUpTransferManager.sol @@ -0,0 +1,651 @@ +pragma solidity ^0.4.24; + +import "./TransferManager/ITransferManager.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +contract LockUpTransferManager is ITransferManager { + + using SafeMath for uint256; + + // permission definition + bytes32 public constant ADMIN = "ADMIN"; + + // a per-user lockup + struct LockUp { + uint256 lockupAmount; // Amount to be locked + uint256 startTime; // when this lockup starts (seconds) + uint256 lockUpPeriodSeconds; // total period of lockup (seconds) + uint256 releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) + } + + // mapping use to store the lockup details corresponds to lockup name + mapping (bytes32 => LockUp) public lockups; + // mapping user addresses to an array of lockups name for that user + mapping (address => bytes32[]) internal userToLockups; + // get list of the addresses for a particular lockupName + mapping (bytes32 => address[]) internal lockupToUsers; + // holds lockup index corresponds to user address. userAddress => lockupName => lockupIndex + mapping (address => mapping(bytes32 => uint256)) internal userToLockupIndex; + // holds the user address index corresponds to the lockup. lockupName => userAddress => userIndex + mapping (bytes32 => mapping(address => uint256)) internal lockupToUserIndex; + + bytes32[] lockupArray; + + event AddNewLockUpToUser( + address indexed userAddress, + bytes32 indexed lockupName + ); + + event RemoveLockUpFromUser( + address indexed userAddress, + bytes32 indexed lockupName + ); + + event ModifyLockUpType( + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + bytes32 indexed lockupName + ); + + event AddNewLockUpType( + bytes32 indexed lockupName, + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds + ); + + event RemoveLockUpType(bytes32 indexed lockupName); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor (address _securityToken, address _polyAddress) + public + Module(_securityToken, _polyAddress) + { + } + + /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred + * @param _from Address of the sender + * @param _amount The amount of tokens to transfer + */ + function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool /*_isTransfer*/) public returns(Result) { + // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user + if (!paused && _from != address(0) && userToLockups[_from].length != 0) { + // check if this transfer is valid + return _checkIfValidTransfer(_from, _amount); + } + return Result.NA; + } + + /** + * @notice Use to add the new lockup type + * @param _lockupAmount Amount of tokens that need to lock. + * @param _startTime When this lockup starts (seconds) + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + * @param _lockupName Name of the lockup + */ + function addNewLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + public + withPerm(ADMIN) + { + _addNewLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + /** + * @notice Use to add the new lockup type + * @param _lockupAmounts Array of amount of tokens that need to lock. + * @param _startTimes Array of startTimes when this lockup starts (seconds) + * @param _lockUpPeriodsSeconds Array of total period of lockup (seconds) + * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) + * @param _lockupNames Array of names of the lockup + */ + function addNewLockUpTypeMulti( + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + bytes32[] _lockupNames + ) + external + withPerm(ADMIN) + { + require( + _lockupNames.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _lockupAmounts.length, + "Input array length mismatch" + ); + for (uint256 i = 0; i < _lockupNames.length; i++) { + _addNewLockUpType( + _lockupAmounts[i], + _startTimes[i], + _lockUpPeriodsSeconds[i], + _releaseFrequenciesSeconds[i], + _lockupNames[i] + ); + } + } + + /** + * @notice Add the lockup to a user + * @param _userAddress Address of the user + * @param _lockupName Name of the lockup + */ + function addLockUpByName( + address _userAddress, + bytes32 _lockupName + ) + public + withPerm(ADMIN) + { + _addLockUpByName(_userAddress, _lockupName); + } + + /** + * @notice Add the lockup to a user + * @param _userAddresses Address of the user + * @param _lockupNames Name of the lockup + */ + function addLockUpByNameMulti( + address[] _userAddresses, + bytes32[] _lockupNames + ) + external + withPerm(ADMIN) + { + require(_userAddresses.length == _lockupNames.length, "Length mismatch"); + for (uint256 i = 0; i < _userAddresses.length; i++) { + _addLockUpByName(_userAddresses[i], _lockupNames[i]); + } + } + + /** + * @notice Lets the admin create a volume restriction lockup for a given address. + * @param _userAddress Address of the user whose tokens should be locked up + * @param _lockupAmount Amount of tokens that need to lock. + * @param _startTime When this lockup starts (seconds) + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + * @param _lockupName Name of the lockup + */ + function addNewLockUpToUser( + address _userAddress, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + external + withPerm(ADMIN) + { + _addNewLockUpToUser( + _userAddress, + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + /** + * @notice Lets the admin create multiple volume restriction lockups for multiple given addresses. + * @param _userAddresses Array of address of the user whose tokens should be locked up + * @param _lockupAmounts Array of the amounts that need to be locked for the different addresses. + * @param _startTimes Array of When this lockup starts (seconds) + * @param _lockUpPeriodsSeconds Array of total periods of lockup (seconds) + * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) + * @param _lockupNames Array of names of the lockup + */ + function addNewLockUpToUserMulti( + address[] _userAddresses, + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + bytes32[] _lockupNames + ) + public + withPerm(ADMIN) + { + require( + _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _lockupAmounts.length && + _userAddresses.length == _lockupNames.length, + "Input array length mismatch" + ); + for (uint256 i = 0; i < _userAddresses.length; i++) { + _addNewLockUpToUser(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i], _lockupNames[i]); + } + } + + /** + * @notice Lets the admin remove a user's lock up + * @param _userAddress Address of the user whose tokens are locked up + * @param _lockupName Name of the lockup need to be removed. + */ + function removeLockUpFromUser(address _userAddress, bytes32 _lockupName) external withPerm(ADMIN) { + _removeLockUpFromUser(_userAddress, _lockupName); + } + + /** + * @notice Used to remove the lockup type + * @param _lockupName Name of the lockup + */ + function removeLockupType(bytes32 _lockupName) external withPerm(ADMIN) { + _removeLockupType(_lockupName); + } + + /** + * @notice Used to remove the multiple lockup type + * @param _lockupNames Array of the lockup names. + */ + function removeLockupTypeMulti(bytes32[] _lockupNames) external withPerm(ADMIN) { + for (uint256 i = 0; i < _lockupNames.length; i++) { + _removeLockupType(_lockupNames[i]); + } + } + + /** + * @notice Use to remove the lockup for multiple users + * @param _userAddresses Array of addresses of the user whose tokens are locked up + * @param _lockupNames Array of the names of the lockup that needs to be removed. + */ + function removeLockUpFromUserMulti(address[] _userAddresses, bytes32[] _lockupNames) external withPerm(ADMIN) { + require(_userAddresses.length == _lockupNames.length, "Array length mismatch"); + for (uint256 i = 0; i < _userAddresses.length; i++) { + _removeLockUpFromUser(_userAddresses[i], _lockupNames[i]); + } + } + + /** + * @notice Lets the admin modify a lockup. + * @param _lockupAmount Amount of tokens that needs to be locked + * @param _startTime When this lockup starts (seconds) + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + * @param _lockupName name of the lockup that needs to be modified. + */ + function modifyLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + external + withPerm(ADMIN) + { + _modifyLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + /** + * @notice Lets the admin modify a volume restriction lockup for a multiple address. + * @param _lockupAmounts Array of the amount of tokens that needs to be locked for the respective addresses. + * @param _startTimes Array of the start time of the lockups (seconds) + * @param _lockUpPeriodsSeconds Array of unix timestamp for the list of lockups (seconds). + * @param _releaseFrequenciesSeconds How often to release a tranche of tokens (seconds) + * @param _lockupNames Array of the lockup names that needs to be modified + */ + function modifyLockUpTypeMulti( + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + bytes32[] _lockupNames + ) + public + withPerm(ADMIN) + { + require( + _lockupNames.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _lockupAmounts.length, + "Input array length mismatch" + ); + for (uint256 i = 0; i < _lockupNames.length; i++) { + _modifyLockUpType( + _lockupAmounts[i], + _startTimes[i], + _lockUpPeriodsSeconds[i], + _releaseFrequenciesSeconds[i], + _lockupNames[i] + ); + } + } + + /** + * @notice Get the length of the lockups array for a specific user address + * @param _userAddress Address of the user whose tokens should be locked up + */ + function getLockUpsLength(address _userAddress) public view returns (uint256) { + return userToLockups[_userAddress].length; + } + + /** + * @notice Get a specific element in a user's lockups array given the user's address and the element index + * @param _userAddress Address of the user whose tokens should be locked up + * @param _lockupName The name of the lockup + */ + function getLockUp(address _userAddress, bytes32 _lockupName) public view returns ( + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + uint256 unlockedAmount + ) { + require( + userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, + "User not assosicated with given lockup" + ); + bytes32[] memory userLockupNames = userToLockups[_userAddress]; + return ( + lockups[_lockupName].lockupAmount, + lockups[_lockupName].startTime, + lockups[_lockupName].lockUpPeriodSeconds, + lockups[_lockupName].releaseFrequencySeconds, + _getUnlockedAmountForLockup(_lockupName) + ); + } + + /** + * @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 + */ + function getListOfAddresses(bytes32 _lockupName) external view returns(address[]) { + require(lockups[_lockupName].startTime != 0, "Blacklist type doesn't exist"); + return lockupToUsers[_lockupName]; + } + + /** + * @notice get the list of lockups names + * @return bytes32 Array of lockups names + */ + function getAllLockups() external view returns(bytes32[]) { + return lockupArray; + } + + /** + * @notice get the list of the lockups for a given user + * @param _user Address of the user + * @return bytes32 List of lockups names associated with the given address + */ + function getLockupsNamesToUser(address _user) external view returns(bytes32[]) { + return userToLockups[_user]; + } + + /** + * @notice Use to get the total locked tokens for a given user + * @param _userAddress Address of the user + * @return uint256 Total locked tokens amount + */ + function getLockedTokenToUser(address _userAddress) public view returns(uint256) { + require(_userAddress != address(0), "Invalid address"); + 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])); + // aggregating all the remaining locked amount for all the lockups for a given address + totalRemainingLockedAmount = totalRemainingLockedAmount.add(remainingLockedAmount); + } + return totalRemainingLockedAmount; + } + + /** + * @notice Checks whether the transfer is allowed + * @param _userAddress Address of the user whose lock ups should be checked + * @param _amount Amount of tokens that need to transact + */ + function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); + // Present balance of the user + uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); + if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { + return Result.NA; + } + return Result.INVALID; + } + + /** + * @notice Provide the unlock amount for the given lockup for a particular user + */ + function _getUnlockedAmountForLockup(bytes32 _lockupName) internal view returns (uint256) { + /*solium-disable-next-line security/no-block-members*/ + if (lockups[_lockupName].startTime > now) { + return 0; + } else if (lockups[_lockupName].startTime.add(lockups[_lockupName].lockUpPeriodSeconds) <= now) { + return lockups[_lockupName].lockupAmount; + } else { + // Calculate the no. of periods for a lockup + uint256 noOfPeriods = (lockups[_lockupName].lockUpPeriodSeconds).div(lockups[_lockupName].releaseFrequencySeconds); + // Calculate the transaction time lies in which period + /*solium-disable-next-line security/no-block-members*/ + uint256 elapsedPeriod = (now.sub(lockups[_lockupName].startTime)).div(lockups[_lockupName].releaseFrequencySeconds); + // Calculate the allowed unlocked amount per period + uint256 amountPerPeriod = (lockups[_lockupName].lockupAmount).div(noOfPeriods); + // Find out the unlocked amount for a given lockup + uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); + return unLockedAmount; + } + } + + function _removeLockupType(bytes32 _lockupName) internal { + require(lockups[_lockupName].startTime != 0, "Lockup type doesn’t exist"); + require(lockupToUsers[_lockupName].length == 0, "Users are associated with the lockup"); + // delete lockup type + delete(lockups[_lockupName]); + uint256 i = 0; + for (i = 0; i < lockupArray.length; i++) { + if (lockupArray[i] == _lockupName) { + break; + } + } + if (i != lockupArray.length -1) { + lockupArray[i] = lockupArray[lockupArray.length -1]; + } + lockupArray.length--; + emit RemoveLockUpType(_lockupName); + } + + function _modifyLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + /*solium-disable-next-line security/no-block-members*/ + require(lockups[_lockupName].startTime > now, "Not allowed to modify"); + require(_startTime >= now, "Past time is not allowed"); + + _checkLockUpParams( + _lockupAmount, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + + lockups[_lockupName] = LockUp( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + + emit ModifyLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + function _removeLockUpFromUser(address _userAddress, bytes32 _lockupName) internal { + require(_userAddress != address(0), "Invalid address"); + require(_lockupName != bytes32(0), "Invalid lockup name"); + require( + userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, + "User not assosicated with given lockup" + ); + + // delete the user from the lockup type + uint256 _lockupIndex = lockupToUserIndex[_lockupName][_userAddress]; + uint256 _len = lockupToUsers[_lockupName].length; + if ( _lockupIndex != _len) { + lockupToUsers[_lockupName][_lockupIndex] = lockupToUsers[_lockupName][_len - 1]; + lockupToUserIndex[_lockupName][lockupToUsers[_lockupName][_lockupIndex]] = _lockupIndex; + } + lockupToUsers[_lockupName].length--; + // delete the user index from the lockup + delete(lockupToUserIndex[_lockupName][_userAddress]); + // delete the lockup from the user + uint256 _userIndex = userToLockupIndex[_userAddress][_lockupName]; + _len = userToLockups[_userAddress].length; + if ( _userIndex != _len) { + userToLockups[_userAddress][_userIndex] = userToLockups[_userAddress][_len - 1]; + userToLockupIndex[_userAddress][userToLockups[_userAddress][_userIndex]] = _userIndex; + } + userToLockups[_userAddress].length--; + // delete the lockup index from the user + delete(userToLockupIndex[_userAddress][_lockupName]); + emit RemoveLockUpFromUser(_userAddress, _lockupName); + } + + function _addNewLockUpToUser( + address _userAddress, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + require(_userAddress != address(0), "Invalid address"); + _addNewLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + _addLockUpByName(_userAddress, _lockupName); + } + + function _addLockUpByName( + address _userAddress, + bytes32 _lockupName + ) + internal + { + require(_userAddress != address(0), "Invalid address"); + require(lockups[_lockupName].startTime >= now, "Lockup expired"); + + userToLockupIndex[_userAddress][_lockupName] = userToLockups[_userAddress].length; + lockupToUserIndex[_lockupName][_userAddress] = lockupToUsers[_lockupName].length; + userToLockups[_userAddress].push(_lockupName); + lockupToUsers[_lockupName].push(_userAddress); + emit AddNewLockUpToUser(_userAddress, _lockupName); + } + + function _addNewLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + require(lockups[_lockupName].lockupAmount == 0, "Already exist"); + /*solium-disable-next-line security/no-block-members*/ + require(_startTime >= now, "Past time not allowed"); + _checkLockUpParams(_lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); + lockups[_lockupName] = LockUp(_lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + lockupArray.push(_lockupName); + emit AddNewLockUpType(_lockupName, _lockupAmount, _startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + } + + /** + * @notice Parameter checking function for creating or editing a lockup. + * This function will cause an exception if any of the parameters are bad. + * @param _lockupAmount Amount that needs to be locked + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + */ + function _checkLockUpParams( + uint256 _lockupAmount, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds + ) + internal + view + { + require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); + require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); + require(_lockupAmount != 0, "lockupAmount cannot be zero"); + + // check that the total amount to be released isn't too granular + require( + _lockupAmount % ISecurityToken(securityToken).granularity() == 0, + "The total amount to be released is more granular than allowed by the token" + ); + + // check that releaseFrequencySeconds evenly divides lockUpPeriodSeconds + require( + _lockUpPeriodSeconds % _releaseFrequencySeconds == 0, + "lockUpPeriodSeconds must be evenly divisible by releaseFrequencySeconds" + ); + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + /** + * @notice Returns the permissions flag that are associated with Percentage transfer Manager + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } +} diff --git a/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol b/contracts/modules/TransferManager/LockUpTransferManagerFactory.sol similarity index 98% rename from contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol rename to contracts/modules/TransferManager/LockUpTransferManagerFactory.sol index 2a79e2528..78f2585d8 100644 --- a/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol +++ b/contracts/modules/TransferManager/LockUpTransferManagerFactory.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "./../../ModuleFactory.sol"; +import "../ModuleFactory.sol"; import "./LockUpTransferManager.sol"; /** From 3469a7d3db3d0ea3157374d927cf08dd1c40dfc1 Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 30 Nov 2018 10:48:20 +0530 Subject: [PATCH 08/15] test case improvement --- .../TransferManager/LockUpTransferManager.sol | 3 +- test/w_lockup_transfer_manager.js | 209 +++++++++++++----- 2 files changed, 158 insertions(+), 54 deletions(-) diff --git a/contracts/modules/TransferManager/LockUpTransferManager.sol b/contracts/modules/TransferManager/LockUpTransferManager.sol index b2b940b3d..cb2c4f156 100644 --- a/contracts/modules/TransferManager/LockUpTransferManager.sol +++ b/contracts/modules/TransferManager/LockUpTransferManager.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "./TransferManager/ITransferManager.sol"; +import "./ITransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; contract LockUpTransferManager is ITransferManager { @@ -592,6 +592,7 @@ contract LockUpTransferManager is ITransferManager { ) internal { + require(_lockupName != bytes32(0), "Invalid name"); require(lockups[_lockupName].lockupAmount == 0, "Already exist"); /*solium-disable-next-line security/no-block-members*/ require(_startTime >= now, "Past time not allowed"); diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js index 86031e94b..eb7a76cd6 100644 --- a/test/w_lockup_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -370,12 +370,13 @@ contract('LockUpTransferManager', accounts => { // create a lockup // this will generate an exception because the lockupAmount is zero await catchRevert( - I_LockUpTransferManager.addLockUp( + I_LockUpTransferManager.addNewLockUpToUser( account_investor2, 0, latestTime() + + duration.seconds(1), duration.seconds(400000), duration.seconds(100000), + "a_lockup", { from: token_owner } @@ -387,12 +388,13 @@ contract('LockUpTransferManager', accounts => { // create a lockup // this will generate an exception because the releaseFrequencySeconds is zero await catchRevert( - I_LockUpTransferManager.addLockUp( + I_LockUpTransferManager.addNewLockUpToUser( account_investor2, web3.utils.toWei('1', 'ether'), latestTime() + duration.seconds(1), duration.seconds(400000), 0, + "a_lockup", { from: token_owner } @@ -404,12 +406,13 @@ contract('LockUpTransferManager', accounts => { // create a lockup // this will generate an exception because the lockUpPeriodSeconds is zero await catchRevert( - I_LockUpTransferManager.addLockUp( + I_LockUpTransferManager.addNewLockUpToUser( account_investor2, web3.utils.toWei('1', 'ether'), latestTime() + duration.seconds(1), 0, duration.seconds(100000), + "a_lockup", { from: token_owner } @@ -422,12 +425,13 @@ contract('LockUpTransferManager', accounts => { // create a lockup // this will generate an exception because we're locking up 5e17 tokens but the granularity is 5e18 tokens await catchRevert( - I_LockUpTransferManager.addLockUp( + I_LockUpTransferManager.addNewLockUpToUser( account_investor2, web3.utils.toWei('0.5', 'ether'), latestTime() + + duration.seconds(1), duration.seconds(400000), duration.seconds(100000), + "a_lockup", { from: token_owner } @@ -435,35 +439,82 @@ contract('LockUpTransferManager', accounts => { ); }); + it("Should add the lockup type -- fail because of bad owner", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpType( + web3.utils.toWei('12', 'ether'), + latestTime() + duration.days(1), + 60, + 20, + "a_lockup", + { + from: account_investor1 + } + ) + ); + }) + + it("Should add the new lockup type", async() => { + let tx = await I_LockUpTransferManager.addNewLockUpType( + web3.utils.toWei('12', 'ether'), + latestTime() + duration.days(1), + 60, + 20, + "a_lockup", + { + from: token_owner + } + ); + assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('12', 'ether')); + }); + + it("Should fail to add the creation of the lockup where lockupName is already exists", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor1, + web3.utils.toWei('5', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + "a_lockup", + { + from: token_owner + } + ) + ); + }) + it("Should allow the creation of a lockup where the lockup amount is divisible" , async() => { // create a lockup - let tx = await I_LockUpVolumeRestrictionTM_div.addLockUp( + let tx = await I_LockUpVolumeRestrictionTM_div.addNewLockUpToUser( account_investor1, web3.utils.toWei('0.5', 'ether'), latestTime() + duration.seconds(1), duration.seconds(400000), duration.seconds(100000), + "a_lockup", { from: token_owner } ); - assert.equal(tx.logs[0].args.userAddress, account_investor1); + assert.equal(tx.logs[1].args.userAddress, account_investor1); assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('0.5', 'ether')); }); it("Should allow the creation of a lockup where the lockup amount is prime no", async() => { // create a lockup - let tx = await I_LockUpVolumeRestrictionTM_div.addLockUp( + let tx = await I_LockUpVolumeRestrictionTM_div.addNewLockUpToUser( account_investor1, web3.utils.toWei('64951', 'ether'), latestTime() + duration.seconds(1), duration.seconds(400000), duration.seconds(100000), + "b_lockup", { from: token_owner } ); - assert.equal(tx.logs[0].args.userAddress, account_investor1); + assert.equal(tx.logs[1].args.userAddress, account_investor1); assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); }); @@ -477,12 +528,13 @@ contract('LockUpTransferManager', accounts => { // over 17 seconds total, with 4 periods. // this will generate an exception because 17 is not evenly divisble by 4. await catchRevert( - I_LockUpTransferManager.addLockUp( + I_LockUpTransferManager.addNewLockUpToUser( account_investor2, web3.utils.toWei('4', 'ether'), latestTime(), 17, 4, + "b_lockup", { from: token_owner } @@ -496,18 +548,19 @@ contract('LockUpTransferManager', accounts => { console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // create a lockup for their entire balance // over 12 seconds total, with 3 periods of 20 seconds each. - await I_LockUpTransferManager.addLockUp( + await I_LockUpTransferManager.addNewLockUpToUser( account_investor2, balance, latestTime() + duration.seconds(1), 60, 20, + "b_lockup", { from: token_owner } ); await increaseTime(2); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) @@ -517,7 +570,7 @@ contract('LockUpTransferManager', accounts => { it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { // wait 20 seconds await increaseTime(duration.seconds(20)); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) @@ -525,13 +578,13 @@ contract('LockUpTransferManager', accounts => { }); it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); it("Should again transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); @@ -540,7 +593,7 @@ contract('LockUpTransferManager', accounts => { // wait 20 more seconds + 1 to get rid of same block time await increaseTime(duration.seconds(21)); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, 0); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); }); @@ -580,12 +633,13 @@ contract('LockUpTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpTransferManager.addLockUpMulti( + I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], [60, 45], [20, 15], + ["c_lockup", "d_lockup"], { from: token_owner } @@ -595,12 +649,13 @@ contract('LockUpTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpTransferManager.addLockUpMulti( + I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], [60, 45], [20, 15], + ["c_lockup", "d_lockup"], { from: token_owner } @@ -610,12 +665,13 @@ contract('LockUpTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpTransferManager.addLockUpMulti( + I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], [60, 45, 50], [20, 15], + ["c_lockup", "d_lockup"], { from: token_owner } @@ -625,12 +681,13 @@ contract('LockUpTransferManager', accounts => { it("Should fail to add the multiple lockups -- because array length mismatch", async() => { await catchRevert( - I_LockUpTransferManager.addLockUpMulti( + I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], [60, 45, 50], [20, 15, 10], + ["c_lockup", "d_lockup"], { from: token_owner } @@ -638,21 +695,38 @@ contract('LockUpTransferManager', accounts => { ); }) + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + ["c_lockup"], + { + from: token_owner + } + ) + ); + }); + it("Should add the multiple lockup to a address", async() => { - await I_LockUpTransferManager.addLockUpMulti( + await I_LockUpTransferManager.addNewLockUpToUserMulti( [account_investor3, account_investor3], [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], [60, 45], [20, 15], + ["c_lockup", "d_lockup"], { from: token_owner } ); await increaseTime(1); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor3, 0); - let tx2 = await I_LockUpTransferManager.getLockUp.call(account_investor3, 1); + let tx = await I_LockUpTransferManager.getLockUp.call(account_investor3, "c_lockup"); + let tx2 = await I_LockUpTransferManager.getLockUp.call(account_investor3, "d_lockup"); console.log("Total Amount get unlocked:", (tx[4].toNumber()) + (tx2[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor3 }) @@ -693,9 +767,9 @@ contract('LockUpTransferManager', accounts => { it("Should remove multiple lockup --failed because of bad owner", async() => { await catchRevert( - I_LockUpTransferManager.removeLockUpMulti( + I_LockUpTransferManager.removeLockUpFromUserMulti( [account_investor3, account_investor3], - [0,1], + ["c_lockup", "d_lockup"], { from: account_polymath } @@ -703,11 +777,11 @@ contract('LockUpTransferManager', accounts => { ); }); - it("Should remove the multiple lockup -- failed because of invalid index", async() => { + it("Should remove the multiple lockup -- failed because of invalid lockupname", async() => { await catchRevert( - I_LockUpTransferManager.removeLockUpMulti( + I_LockUpTransferManager.removeLockUpFromUserMulti( [account_investor3, account_investor3], - [0,3], + ["c_lockup", "e_lockup"], { from: account_polymath } @@ -716,9 +790,9 @@ contract('LockUpTransferManager', accounts => { }) it("Should remove the multiple lockup", async() => { - await I_LockUpTransferManager.removeLockUpMulti( + await I_LockUpTransferManager.removeLockUpFromUserMulti( [account_investor3, account_investor3], - [1,0], + ["d_lockup", "c_lockup"], { from: token_owner } @@ -730,26 +804,26 @@ contract('LockUpTransferManager', accounts => { it("Should fail to modify the lockup -- because of bad owner", async() => { await I_SecurityToken.mint(account_investor3, web3.utils.toWei("9"), {from: token_owner}); - let tx = await I_LockUpTransferManager.addLockUp( + let tx = await I_LockUpTransferManager.addNewLockUpToUser( account_investor3, web3.utils.toWei("9"), latestTime() + duration.minutes(5), 60, 20, + "z_lockup", { from: token_owner } ); - temp = (tx.logs[0].args.lockupIndex).toNumber(); + await catchRevert( // edit the lockup - I_LockUpTransferManager.modifyLockUp( - account_investor3, + I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), latestTime() + duration.seconds(1), 60, 20, - (tx.logs[0].args.lockupIndex).toNumber(), + "z_lockup", { from: account_polymath } @@ -760,13 +834,12 @@ contract('LockUpTransferManager', accounts => { it("Modify the lockup when startTime is in past -- failed because startTime is in the past", async() => { await catchRevert( // edit the lockup - I_LockUpTransferManager.modifyLockUp( - account_investor3, + I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), latestTime() - duration.seconds(50), 60, 20, - temp, + "z_lockup", { from: token_owner } @@ -777,13 +850,12 @@ contract('LockUpTransferManager', accounts => { it("Modify the lockup when startTime is in past -- failed because of invalid index", async() => { await catchRevert( // edit the lockup - I_LockUpTransferManager.modifyLockUp( - account_investor3, + I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), latestTime() + duration.seconds(50), 60, 20, - 6, + "m_lockup", { from: token_owner } @@ -793,13 +865,12 @@ contract('LockUpTransferManager', accounts => { it("should successfully modify the lockup", async() => { // edit the lockup - await I_LockUpTransferManager.modifyLockUp( - account_investor3, + await I_LockUpTransferManager.modifyLockUpType( web3.utils.toWei("9"), latestTime() + duration.seconds(50), 60, 20, - temp, + "z_lockup", { from: token_owner } @@ -815,12 +886,13 @@ contract('LockUpTransferManager', accounts => { // create a lockup for their entire balance // over 16 seconds total, with 4 periods of 4 seconds each. - await I_LockUpTransferManager.addLockUp( + await I_LockUpTransferManager.addNewLockUpToUser( account_investor1, balance, latestTime() + duration.minutes(5), 60, 20, + "f_lockup", { from: token_owner } @@ -834,7 +906,7 @@ contract('LockUpTransferManager', accounts => { let lockUpCount = await I_LockUpTransferManager.getLockUpsLength(account_investor1); assert.equal(lockUpCount, 1) - let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, 0); + let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, "f_lockup"); console.log(lockUp); // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount assert.equal( @@ -847,13 +919,12 @@ contract('LockUpTransferManager', accounts => { // edit the lockup temp = latestTime() + duration.seconds(1); - await I_LockUpTransferManager.modifyLockUp( - account_investor1, + await I_LockUpTransferManager.modifyLockUpType( balance, temp, 60, 20, - 0, + "f_lockup", { from: token_owner } @@ -874,17 +945,16 @@ contract('LockUpTransferManager', accounts => { it("Modify the lockup during the lockup periods --fail because already tokens get unlocked", async() => { let balance = await I_SecurityToken.balanceOf(account_investor1) - let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, 0); + let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, "f_lockup"); console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // edit the lockup await catchRevert( - I_LockUpTransferManager.modifyLockUp( - account_investor1, + I_LockUpTransferManager.modifyLockUpType( balance, temp, 90, 30, - 0, + "f_lockup", { from: token_owner } @@ -892,7 +962,34 @@ contract('LockUpTransferManager', accounts => { ); }); - it("Should succesfully get the lockup - fail because array index out of bound", async() => { + it("Should remove the lockup multi", async() => { + await I_LockUpTransferManager.addNewLockUpTypeMulti( + [web3.utils.toWei("10"), web3.utils.toWei("16")], + [latestTime() + duration.days(1), latestTime() + duration.days(1)], + [50000, 50000], + [10000, 10000], + ["k_lockup", "l_lockup"], + { + from: token_owner + } + ); + + // removing the lockup type + let tx = await I_LockUpTransferManager.removeLockupType("k_lockup", {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args.lockupName), "k_lockup"); + + // attaching the lockup to a user + + await I_LockUpTransferManager.addLockUpByName(account_investor2, "l_lockup", {from: token_owner}); + + // try to delete the lockup but fail + + await catchRevert( + I_LockUpTransferManager.removeLockupType("l_lockup", {from: token_owner}) + ); + }) + + it("Should succesfully get the lockup - fail because no lockup exist for the user of this name", async() => { await catchRevert( I_LockUpTransferManager.getLockUp(account_investor1, 9) ); @@ -903,6 +1000,12 @@ contract('LockUpTransferManager', accounts => { assert.equal(web3.utils.hexToNumber(sig), 0); }); + it("Should get the all lockups added by the issuer till now", async() => { + let tx = await I_LockUpTransferManager.getAllLockups.call(); + for (let i = 0; i < tx.length; i++) { + console.log(web3.utils.toUtf8(tx[i])); + } + }) it("Should get the permission", async() => { let perm = await I_LockUpTransferManager.getPermissions.call(); From ae08093b5e4be98e87e707b47b83f15a59365433 Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 30 Nov 2018 15:27:53 +0530 Subject: [PATCH 09/15] minor fixes --- .../TransferManager/LockUpTransferManager.sol | 19 +------- test/w_lockup_transfer_manager.js | 43 ------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/contracts/modules/TransferManager/LockUpTransferManager.sol b/contracts/modules/TransferManager/LockUpTransferManager.sol index cb2c4f156..1de5a0c14 100644 --- a/contracts/modules/TransferManager/LockUpTransferManager.sol +++ b/contracts/modules/TransferManager/LockUpTransferManager.sol @@ -368,7 +368,6 @@ contract LockUpTransferManager is ITransferManager { userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, "User not assosicated with given lockup" ); - bytes32[] memory userLockupNames = userToLockups[_userAddress]; return ( lockups[_lockupName].lockupAmount, lockups[_lockupName].startTime, @@ -454,10 +453,8 @@ contract LockUpTransferManager is ITransferManager { // Calculate the transaction time lies in which period /*solium-disable-next-line security/no-block-members*/ uint256 elapsedPeriod = (now.sub(lockups[_lockupName].startTime)).div(lockups[_lockupName].releaseFrequencySeconds); - // Calculate the allowed unlocked amount per period - uint256 amountPerPeriod = (lockups[_lockupName].lockupAmount).div(noOfPeriods); // Find out the unlocked amount for a given lockup - uint256 unLockedAmount = elapsedPeriod.mul(amountPerPeriod); + uint256 unLockedAmount = (lockups[_lockupName].lockupAmount.mul(elapsedPeriod)).div(noOfPeriods); return unLockedAmount; } } @@ -615,23 +612,11 @@ contract LockUpTransferManager is ITransferManager { uint256 _releaseFrequencySeconds ) internal - view + pure { require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); require(_lockupAmount != 0, "lockupAmount cannot be zero"); - - // check that the total amount to be released isn't too granular - require( - _lockupAmount % ISecurityToken(securityToken).granularity() == 0, - "The total amount to be released is more granular than allowed by the token" - ); - - // check that releaseFrequencySeconds evenly divides lockUpPeriodSeconds - require( - _lockUpPeriodSeconds % _releaseFrequencySeconds == 0, - "lockUpPeriodSeconds must be evenly divisible by releaseFrequencySeconds" - ); } /** diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js index eb7a76cd6..834ab8cca 100644 --- a/test/w_lockup_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -420,25 +420,6 @@ contract('LockUpTransferManager', accounts => { ); }); - - it("Should prevent the creation of a lockup with bad parameters where the total amount to be released is more granular than allowed by the token", async() => { - // create a lockup - // this will generate an exception because we're locking up 5e17 tokens but the granularity is 5e18 tokens - await catchRevert( - I_LockUpTransferManager.addNewLockUpToUser( - account_investor2, - web3.utils.toWei('0.5', 'ether'), - latestTime() + + duration.seconds(1), - duration.seconds(400000), - duration.seconds(100000), - "a_lockup", - { - from: token_owner - } - ) - ); - }); - it("Should add the lockup type -- fail because of bad owner", async() => { await catchRevert( I_LockUpTransferManager.addNewLockUpType( @@ -518,30 +499,6 @@ contract('LockUpTransferManager', accounts => { assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); }); - it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is not evenly divisible by releaseFrequencySeconds", async() => { - - // balance should be 9000000000000000000 here (9 eth) - let balance = await I_SecurityToken.balanceOf(account_investor2) - - - // create a lockup - // over 17 seconds total, with 4 periods. - // this will generate an exception because 17 is not evenly divisble by 4. - await catchRevert( - I_LockUpTransferManager.addNewLockUpToUser( - account_investor2, - web3.utils.toWei('4', 'ether'), - latestTime(), - 17, - 4, - "b_lockup", - { - from: token_owner - } - ) - ); - }); - it("Should prevent the transfer of tokens in a lockup", async() => { let balance = await I_SecurityToken.balanceOf(account_investor2) From 57c24be626af78bd610021579f8669dd8084932f Mon Sep 17 00:00:00 2001 From: satyam Date: Fri, 30 Nov 2018 19:41:19 +0530 Subject: [PATCH 10/15] allow the lockup start could be 0 --- .../TransferManager/LockUpTransferManager.sol | 23 +++++++++++++------ test/w_lockup_transfer_manager.js | 8 +++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/contracts/modules/TransferManager/LockUpTransferManager.sol b/contracts/modules/TransferManager/LockUpTransferManager.sol index 1de5a0c14..3d3450721 100644 --- a/contracts/modules/TransferManager/LockUpTransferManager.sol +++ b/contracts/modules/TransferManager/LockUpTransferManager.sol @@ -487,8 +487,13 @@ contract LockUpTransferManager is ITransferManager { internal { /*solium-disable-next-line security/no-block-members*/ - require(lockups[_lockupName].startTime > now, "Not allowed to modify"); - require(_startTime >= now, "Past time is not allowed"); + uint256 startTime = _startTime; + + if (_startTime == 0) { + startTime = now; + } + require(startTime >= now, "Invalid start time"); + require(lockups[_lockupName].lockupAmount != 0, "Doesn't exist"); _checkLockUpParams( _lockupAmount, @@ -498,14 +503,14 @@ contract LockUpTransferManager is ITransferManager { lockups[_lockupName] = LockUp( _lockupAmount, - _startTime, + startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds ); emit ModifyLockUpType( _lockupAmount, - _startTime, + startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds, _lockupName @@ -589,14 +594,18 @@ contract LockUpTransferManager is ITransferManager { ) 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*/ - require(_startTime >= now, "Past time not allowed"); + if (_startTime == 0) { + startTime = now; + } + require(startTime >= now, "Invalid start time"); _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); } /** diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js index 834ab8cca..59e1ed816 100644 --- a/test/w_lockup_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -900,22 +900,20 @@ contract('LockUpTransferManager', accounts => { }); - it("Modify the lockup during the lockup periods --fail because already tokens get unlocked", async() => { + it("Modify the lockup during the lockup periods", async() => { let balance = await I_SecurityToken.balanceOf(account_investor1) let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, "f_lockup"); console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // edit the lockup - await catchRevert( - I_LockUpTransferManager.modifyLockUpType( + await I_LockUpTransferManager.modifyLockUpType( balance, - temp, + latestTime() + duration.days(10), 90, 30, "f_lockup", { from: token_owner } - ) ); }); From 41627660fcd4a5a477986700b52e652a3e83bce5 Mon Sep 17 00:00:00 2001 From: Victor Date: Tue, 11 Dec 2018 15:18:30 -0300 Subject: [PATCH 11/15] CLI - LockupTM support --- CLI/commands/helpers/contract_abis.js | 5 + CLI/commands/transfer_manager.js | 608 ++++++++++++++---- .../Transfer/LockupTM/add_lockup_data.csv | 4 + .../LockupTM/add_lockup_investor_data.csv | 12 + .../Transfer/LockupTM/delete_lockup_data.csv | 2 + .../Transfer/LockupTM/modify_lockup_data.csv | 4 + .../LockupTM/remove_lockup_investor_data.csv | 12 + 7 files changed, 536 insertions(+), 111 deletions(-) create mode 100644 CLI/data/Transfer/LockupTM/add_lockup_data.csv create mode 100644 CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv create mode 100644 CLI/data/Transfer/LockupTM/delete_lockup_data.csv create mode 100644 CLI/data/Transfer/LockupTM/modify_lockup_data.csv create mode 100644 CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index 8a551b70b..934be67fd 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -10,6 +10,7 @@ let generalTransferManagerABI; let manualApprovalTransferManagerABI; let countTransferManagerABI; let percentageTransferManagerABI; +let lockUpTransferManagerABI; let generalPermissionManagerABI; let polyTokenABI; let cappedSTOFactoryABI; @@ -35,6 +36,7 @@ try { manualApprovalTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/ManualApprovalTransferManager.json').toString()).abi; countTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/CountTransferManager.json').toString()).abi; percentageTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/PercentageTransferManager.json').toString()).abi; + lockUpTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/LockUpTransferManager.json').toString()).abi; generalPermissionManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/GeneralPermissionManager.json').toString()).abi; polyTokenABI = JSON.parse(require('fs').readFileSync('./build/contracts/PolyTokenFaucet.json').toString()).abi; cappedSTOFactoryABI = JSON.parse(require('fs').readFileSync('./build/contracts/CappedSTOFactory.json').toString()).abi; @@ -88,6 +90,9 @@ module.exports = { percentageTransferManager: function () { return percentageTransferManagerABI; }, + lockUpTransferManager: function () { + return lockUpTransferManagerABI; + }, generalPermissionManager: function () { return generalPermissionManagerABI; }, diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 16af0e34e..8fe30206e 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -6,12 +6,17 @@ const contracts = require('./helpers/contract_addresses'); const abis = require('./helpers/contract_abis'); const gbl = require('./common/global'); const csvParse = require('./helpers/csv'); -const { table } = require('table') +const { table } = require('table'); /////////////////// // Constants const WHITELIST_DATA_CSV = './CLI/data/Transfer/GTM/whitelist_data.csv'; const PERCENTAGE_WHITELIST_DATA_CSV = './CLI/data/Transfer/PercentageTM/whitelist_data.csv'; +const ADD_LOCKUP_DATA_CSV = './CLI/data/Transfer/LockupTM/add_lockup_data.csv'; +const MODIFY_LOCKUP_DATA_CSV = './CLI/data/Transfer/LockupTM/modify_lockup_data.csv'; +const DELETE_LOCKUP_DATA_CSV = './CLI/data/Transfer/LockupTM/delete_lockup_data.csv'; +const ADD_LOCKUP_INVESTOR_DATA_CSV = './CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv'; +const REMOVE_LOCKUP_INVESTOR_DATA_CSV = './CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv'; // App flow let tokenSymbol; @@ -21,98 +26,96 @@ let moduleRegistry; let currentTransferManager; async function executeApp() { - let exit = false; - while (!exit) { - console.log('\n', chalk.blue('Transfer Manager - Main Menu', '\n')); - - let tmModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.TRANSFER); - let nonArchivedModules = tmModules.filter(m => !m.archived); - if (nonArchivedModules.length > 0) { - console.log(`Transfer Manager modules attached:`); - nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) - } else { - console.log(`There are no Transfer Manager modules attached`); - } + console.log('\n', chalk.blue('Transfer Manager - Main Menu', '\n')); - let options = ['Verify transfer', 'Transfer']; - let forcedTransferDisabled = await securityToken.methods.controllerDisabled().call(); - if (!forcedTransferDisabled) { - options.push('Forced transfers'); - } - if (nonArchivedModules.length > 0) { - options.push('Config existing modules'); - } - options.push('Add new Transfer Manager module'); - - let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Exit' }); - let optionSelected = index != -1 ? options[index] : 'Exit'; - console.log('Selected:', optionSelected, '\n'); - switch (optionSelected) { - case 'Verify transfer': - let verifyTotalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); - await logTotalInvestors(); - let verifyTransferFrom = readlineSync.question(`Enter the sender account (${Issuer.address}): `, { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - await logBalance(verifyTransferFrom, verifyTotalSupply); - let verifyTransferTo = readlineSync.question('Enter the receiver account: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - await logBalance(verifyTransferTo, verifyTotalSupply); - let verifyTransferAmount = readlineSync.question('Enter amount of tokens to verify: '); - let isVerified = await securityToken.methods.verifyTransfer(verifyTransferFrom, verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); - if (isVerified) { - console.log(chalk.green(`\n${verifyTransferAmount} ${tokenSymbol} can be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); - } else { - console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); - } - break; - case 'Transfer': - let totalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + let tmModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.TRANSFER); + let nonArchivedModules = tmModules.filter(m => !m.archived); + if (nonArchivedModules.length > 0) { + console.log(`Transfer Manager modules attached:`); + nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + } else { + console.log(`There are no Transfer Manager modules attached`); + } + + let options = ['Verify transfer', 'Transfer']; + let forcedTransferDisabled = await securityToken.methods.controllerDisabled().call(); + if (!forcedTransferDisabled) { + options.push('Forced transfers'); + } + if (nonArchivedModules.length > 0) { + options.push('Config existing modules'); + } + options.push('Add new Transfer Manager module'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); + let optionSelected = index != -1 ? options[index] : 'EXIT'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Verify transfer': + let verifyTotalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + await logTotalInvestors(); + let verifyTransferFrom = readlineSync.question(`Enter the sender account (${Issuer.address}): `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: Issuer.address + }); + await logBalance(verifyTransferFrom, verifyTotalSupply); + let verifyTransferTo = readlineSync.question('Enter the receiver account: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + await logBalance(verifyTransferTo, verifyTotalSupply); + let verifyTransferAmount = readlineSync.question('Enter amount of tokens to verify: '); + let isVerified = await securityToken.methods.verifyTransfer(verifyTransferFrom, verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); + if (isVerified) { + console.log(chalk.green(`\n${verifyTransferAmount} ${tokenSymbol} can be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + } else { + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + } + break; + case 'Transfer': + let totalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + await logTotalInvestors(); + await logBalance(Issuer.address, totalSupply); + let transferTo = readlineSync.question('Enter beneficiary of tranfer: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + await logBalance(transferTo, totalSupply); + let transferAmount = readlineSync.question('Enter amount of tokens to transfer: '); + let isTranferVerified = await securityToken.methods.verifyTransfer(Issuer.address, transferTo, web3.utils.toWei(transferAmount), web3.utils.fromAscii("")).call(); + if (isTranferVerified) { + let transferAction = securityToken.methods.transfer(transferTo, web3.utils.toWei(transferAmount)); + let receipt = await common.sendTransaction(transferAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); + console.log(chalk.green(`${event.from} transferred ${web3.utils.fromWei(event.value)} ${tokenSymbol} to ${event.to} successfully!`)); await logTotalInvestors(); await logBalance(Issuer.address, totalSupply); - let transferTo = readlineSync.question('Enter beneficiary of tranfer: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); await logBalance(transferTo, totalSupply); - let transferAmount = readlineSync.question('Enter amount of tokens to transfer: '); - let isTranferVerified = await securityToken.methods.verifyTransfer(Issuer.address, transferTo, web3.utils.toWei(transferAmount), web3.utils.fromAscii("")).call(); - if (isTranferVerified) { - let transferAction = securityToken.methods.transfer(transferTo, web3.utils.toWei(transferAmount)); - let receipt = await common.sendTransaction(transferAction); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(chalk.green(`${event.from} transferred ${web3.utils.fromWei(event.value)} ${tokenSymbol} to ${event.to} successfully!`)); - await logTotalInvestors(); - await logBalance(Issuer.address, totalSupply); - await logBalance(transferTo, totalSupply); - } else { - console.log(chalk.red(`Transfer failed at verification. Please review the transfer restrictions.`)); - } - break; - case 'Forced transfers': - await forcedTransfers(); - break; - case 'Config existing modules': - await configExistingModules(nonArchivedModules); - break; - case 'Add new Transfer Manager module': - await addTransferManagerModule(); - break; - case 'Exit': - exit = true; - break - } + } else { + console.log(chalk.red(`Transfer failed at verification. Please review the transfer restrictions.`)); + } + break; + case 'Forced transfers': + await forcedTransfers(); + break; + case 'Config existing modules': + await configExistingModules(nonArchivedModules); + break; + case 'Add new Transfer Manager module': + await addTransferManagerModule(); + break; + case 'EXIT': + return; } + + await executeApp(); } async function forcedTransfers() { @@ -221,13 +224,10 @@ async function configExistingModules(tmModules) { *********************************` )); break; - case 'LookupVolumeRestrictionTM': - //await lookupVolumeRestrictionTM(); - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); + case 'LockUpTransferManager': + currentTransferManager = new web3.eth.Contract(abis.lockUpTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await lockUpTransferManager(); break; } } @@ -303,13 +303,6 @@ async function addTransferManagerModule() { *********************************` )); break; - case 'LookupVolumeRestrictionTM': - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); - break; } let selectedTMFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.TRANSFER, options[index]); let addModuleAction = securityToken.methods.addModule(selectedTMFactoryAddress, bytes, 0, 0); @@ -320,7 +313,7 @@ async function addTransferManagerModule() { } async function generalTransferManager() { - console.log(chalk.blue(`General Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + console.log('\n', chalk.blue(`General Transfer Manager at ${currentTransferManager.options.address}`), '\n'); // Show current data let displayIssuanceAddress = await currentTransferManager.methods.issuanceAddress().call(); @@ -567,7 +560,7 @@ async function modifyWhitelistInBatch() { } async function manualApprovalTransferManager() { - console.log(chalk.blue(`Manual Approval Transfer Manager at ${currentTransferManager.options.address} `), '\n'); + console.log('\n', chalk.blue(`Manual Approval Transfer Manager at ${currentTransferManager.options.address} `), '\n'); let options = ['Check manual approval', 'Add manual approval', 'Revoke manual approval', 'Check manual blocking', 'Add manual blocking', 'Revoke manual blocking']; @@ -742,7 +735,7 @@ async function getManualBlocking(_from, _to) { } async function countTransferManager() { - console.log(chalk.blue(`Count Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + console.log('\n', chalk.blue(`Count Transfer Manager at ${currentTransferManager.options.address}`), '\n'); // Show current data let displayMaxHolderCount = await currentTransferManager.methods.maxHolderCount().call(); @@ -765,7 +758,7 @@ async function countTransferManager() { } async function percentageTransferManager() { - console.log(chalk.blue(`Percentage Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + console.log('\n', chalk.blue(`Percentage Transfer Manager at ${currentTransferManager.options.address}`), '\n'); // Show current data let displayMaxHolderPercentage = await currentTransferManager.methods.maxHolderPercentage().call(); @@ -865,7 +858,400 @@ async function percentageTransferManager() { console.log(chalk.green(`Transactions which are part of the primary issuance will NOT be ignored!`)); } break; + } +} + +async function lockUpTransferManager() { + console.log('\n', chalk.blue(`Lockup Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + + let currentLockups = await currentTransferManager.methods.getAllLockups().call(); + console.log(`- Lockups: ${currentLockups.length}`); + + let options = ['Add new lockup']; + if (currentLockups.length > 0) { + options.push('Manage existing lockups', 'Explore investor'); + } + options.push('Operate with multiple lockups'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add new lockup': + let name = readlineSync.question(`Enter the name of the lockup type: `, { + limit: function (input) { + return input !== ""; + }, + limitMessage: `Invalid lockup name` + }); + let lockupAmount = readlineSync.questionInt(`Enter the amount of tokens that will be locked: `); + let minuteFromNow = Math.floor(Date.now() / 1000) + 60; + let startTime = readlineSync.questionInt(`Enter the start time (Unix Epoch time) of the lockup type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); + let lockUpPeriodSeconds = readlineSync.questionInt(`Enter the total period (seconds) of the lockup type (ten minutes = 600): `, { defaultInput: 600 }); + let releaseFrequencySeconds = readlineSync.questionInt(`Enter how often to release a tranche of tokens in seconds (one minute = 60): `, { defaultInput: 60 }); + if (readlineSync.keyInYNStrict(`Do you want to add an investor to this lockup type? `)) { + let investor = readlineSync.question(`Enter the address of the investor: `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: `Must be a valid address` + }); + let addNewLockUpToUserAction = currentTransferManager.methods.addNewLockUpToUser( + investor, + web3.utils.toWei(lockupAmount.toString()), + startTime, + lockUpPeriodSeconds, + releaseFrequencySeconds, + web3.utils.toHex(name) + ); + let addNewLockUpToUserReceipt = await common.sendTransaction(addNewLockUpToUserAction); + let addNewLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddNewLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(addNewLockUpToUserEvent.lockupName)} lockup type has been added successfully!`)); + let addLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddNewLockUpToUser'); + console.log(chalk.green(`${addLockUpToUserEvent.userAddress} has been added to ${web3.utils.hexToUtf8(addLockUpToUserEvent.lockupName)} successfully!`)); + } else { + let addLockupTypeAction = currentTransferManager.methods.addNewLockUpType(web3.utils.toWei(lockupAmount.toString()), startTime, lockUpPeriodSeconds, releaseFrequencySeconds, web3.utils.toHex(name)); + let addLockupTypeReceipt = await common.sendTransaction(addLockupTypeAction); + let addLockupTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addLockupTypeReceipt.logs, 'AddNewLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(addLockupTypeEvent.lockupName)} lockup type has been added successfully!`)); + } + break; + case 'Manage existing lockups': + let options = currentLockups.map(b => web3.utils.hexToUtf8(b)); + let index = readlineSync.keyInSelect(options, 'Which lockup type do you want to manage? ', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + if (index !== -1) { + await manageExistingLockups(currentLockups[index]); + } + break; + case 'Explore investor': + let investorToExplore = readlineSync.question('Enter the address you want to explore: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let lockupsToInvestor = await currentTransferManager.methods.getLockupsNamesToUser(investorToExplore).call(); + if (lockupsToInvestor.length > 0) { + let lockedTokenToInvestor = await currentTransferManager.methods.getLockedTokenToUser(investorToExplore).call(); + console.log(chalk.green(`The address ${investorToExplore} has ${web3.utils.fromWei(lockedTokenToInvestor)} ${tokenSymbol} locked across the following ${lockupsToInvestor.length} lockups: `)); + lockupsToInvestor.map(l => console.log(chalk.green(`- ${web3.utils.hexToUtf8(l)}`))); + } else { + console.log(chalk.yellow(`The address ${investorToExplore} has no lockups`)); + } + break; + case 'Operate with multiple lockups': + await operateWithMultipleLockups(currentLockups); + break; + case 'RETURN': + return; + } + + await lockUpTransferManager(); +} + +async function manageExistingLockups(lockupName) { + console.log('\n', chalk.blue(`Lockup ${web3.utils.hexToUtf8(lockupName)}`), '\n'); + + // Show current data + let currentLockup = await currentTransferManager.methods.lockups(lockupName).call(); + let investors = await currentTransferManager.methods.getListOfAddresses(lockupName).call(); + + console.log(`- Amount: ${web3.utils.fromWei(currentLockup.lockupAmount)} ${tokenSymbol}`) + console.log(`- Start time: ${moment.unix(currentLockup.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- Lockup period: ${currentLockup.lockUpPeriodSeconds} seconds`); + console.log(`- End time: ${moment.unix(currentLockup.startTime).add(parseInt(currentLockup.lockUpPeriodSeconds)).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- Release frequency: ${currentLockup.releaseFrequencySeconds} senconds`); + console.log(`- Investors: ${investors.length}`); + // ------------------ + + let options = [ + 'Modify properties', + 'Show investors', + 'Add this lockup to investors', + 'Remove this lockup from investors', + 'Delete this lockup type' + ]; + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Modify properties': + let lockupAmount = readlineSync.questionInt(`Enter the amount of tokens that will be locked: `); + let minuteFromNow = Math.floor(Date.now() / 1000) + 60; + let startTime = readlineSync.questionInt(`Enter the start time (Unix Epoch time) of the lockup type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); + let lockUpPeriodSeconds = readlineSync.questionInt(`Enter the total period (seconds) of the lockup type (ten minutes = 600): `, { defaultInput: 600 }); + let releaseFrequencySeconds = readlineSync.questionInt(`Enter how often to release a tranche of tokens in seconds (one minute = 60): `, { defaultInput: 60 }); + let modifyLockUpTypeAction = currentTransferManager.methods.modifyLockUpType(lockupAmount, startTime, lockUpPeriodSeconds, releaseFrequencySeconds, lockupName); + let modifyLockUpTypeReceipt = await common.sendTransaction(modifyLockUpTypeAction); + let modifyLockUpTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyLockUpTypeReceipt.logs, 'ModifyLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(modifyLockUpTypeEvent.lockupName)} lockup type has been modified successfully!`)); + break; + case 'Show investors': + if (investors.length > 0) { + console.log("************ List of investors ************"); + investors.map(i => console.log(i)); + } else { + console.log(chalk.yellow("There are no investors yet")); + } + break; + case 'Add this lockup to investors': + let investorsToAdd = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { + limit: function (input) { + return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(","); + let addInvestorToLockupAction; + if (investorsToAdd.length === 1) { + addInvestorToLockupAction = currentTransferManager.methods.addLockUpByName(investorsToAdd[0], lockupName); + } else { + addInvestorToLockupAction = currentTransferManager.methods.addLockUpByNameMulti(investorsToAdd, investorsToAdd.map(i => lockupName)); + } + let addInvestorToLockupReceipt = await common.sendTransaction(addInvestorToLockupAction); + let addInvestorToLockupEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, addInvestorToLockupReceipt.logs, 'AddNewLockUpToUser'); + addInvestorToLockupEvents.map(e => console.log(chalk.green(`${e.userAddress} has been added to ${web3.utils.hexToUtf8(e.lockupName)} successfully!`))); + break; + case 'Remove this lockup from investors': + let investorsToRemove = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { + limit: function (input) { + return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(","); + let removeLockupFromInvestorAction; + if (investorsToRemove.length === 1) { + removeLockupFromInvestorAction = currentTransferManager.methods.removeLockUpFromUser(investorsToRemove[0], lockupName); + } else { + removeLockupFromInvestorAction = currentTransferManager.methods.removeLockUpFromUserMulti(investorsToRemove, investorsToRemove.map(i => lockupName)); + } + let removeLockUpFromUserReceipt = await common.sendTransaction(removeLockupFromInvestorAction); + let removeLockUpFromUserEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, removeLockUpFromUserReceipt.logs, 'RemoveLockUpFromUser'); + removeLockUpFromUserEvents.map(e => console.log(chalk.green(`${e.userAddress} has been removed to ${web3.utils.hexToUtf8(e.lockupName)} successfully!`))); + break; + case 'Delete this lockup type': + let isEmpty = investors.length === 0; + if (!isEmpty) { + console.log(chalk.yellow(`This lockup have investors added to it. To delete it you must remove them first.`)); + if (readlineSync.keyInYNStrict(`Do you want to remove them? `)) { + let data = investors.map(i => [i, lockupName]) + let batches = common.splitIntoBatches(data, gbl.constants.DEFAULT_BATCH_SIZE); + let [investorArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove the following investors:\n\n`, investorArray[batch], '\n'); + let action = currentTransferManager.methods.removeLockUpFromUserMulti(investorArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove lockups from multiple investors transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } + isEmpty = true; + } + } + if (isEmpty) { + let removeLockupTypeAction = currentTransferManager.methods.removeLockupType(lockupName); + let removeLockupTypeReceipt = await common.sendTransaction(removeLockupTypeAction); + let removeLockupTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeLockupTypeReceipt.logs, 'RemoveLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(removeLockupTypeEvent.lockupName)} lockup type has been deleted successfully!`)); + } + return; + case 'RETURN': + return; + } + + await manageExistingLockups(lockupName); +} + +async function operateWithMultipleLockups(currentLockups) { + let options = ['Add multiple lockups']; + if (currentLockups.length > 0) { + options.push('Modify multiple lockups'); + } + options.push( + 'Delete multiple lockups', + 'Add lockups to multiple investors', + 'Remove lockups from multiple investors' + ); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add multiple lockups': + await addLockupsInBatch(); + break; + case 'Modify multiple lockups': + await modifyLockupsInBatch(); + break; + case 'Delete multiple lockups': + await deleteLockupsInBatch(); + break; + case 'Add lockups to multiple investors': + await addLockupsToInvestorsInBatch(); + break; + case 'Remove lockups from multiple investors': + await removeLockupsFromInvestorsInBatch(); + break; + } +} + +async function addLockupsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_LOCKUP_DATA_CSV}): `, { + defaultInput: ADD_LOCKUP_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => !isNaN(row[0]) && + moment.unix(row[1]).isValid() && + (!isNaN(row[2] && (parseFloat(row[2]) % 1 === 0))) && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 0))) && + typeof row[4] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [amountArray, startTimeArray, lockUpPeriodArray, releaseFrequencyArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add the following lockups: \n\n`, lockupNameArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.addNewLockUpTypeMulti(amountArray[batch], startTimeArray[batch], lockUpPeriodArray[batch], releaseFrequencyArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple lockups transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} +async function modifyLockupsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_LOCKUP_DATA_CSV}): `, { + defaultInput: MODIFY_LOCKUP_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => !isNaN(row[0]) && + moment.unix(row[1]).isValid() && + (!isNaN(row[2] && (parseFloat(row[2]) % 1 === 0))) && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 0))) && + typeof row[4] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [amountArray, startTimeArray, lockUpPeriodArray, releaseFrequencyArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify the following lockups: \n\n`, lockupNameArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.modifyLockUpTypeMulti(amountArray[batch], startTimeArray[batch], lockUpPeriodArray[batch], releaseFrequencyArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple lockups transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function deleteLockupsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${DELETE_LOCKUP_DATA_CSV}): `, { + defaultInput: DELETE_LOCKUP_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => typeof row[0] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to delete the following lockups: \n\n`, lockupNameArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.removeLockupTypeMulti(lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Delete multiple lockups transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function addLockupsToInvestorsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_LOCKUP_INVESTOR_DATA_CSV}): `, { + defaultInput: ADD_LOCKUP_INVESTOR_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add lockups to the following investors: \n\n`, investorArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.addLockUpByNameMulti(investorArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add lockups to multiple investors transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function removeLockupsFromInvestorsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_LOCKUP_INVESTOR_DATA_CSV}): `, { + defaultInput: REMOVE_LOCKUP_INVESTOR_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove the following investors: \n\n`, investorArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.removeLockUpFromUserMulti(investorArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove lockups from multiple investors transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); } } @@ -1159,13 +1545,13 @@ async function selectToken() { }); options.push('Enter token symbol manually'); - let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' }); - let selected = index != -1 ? options[index] : 'Exit'; + let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'EXIT' }); + let selected = index != -1 ? options[index] : 'EXIT'; switch (selected) { case 'Enter token symbol manually': result = readlineSync.question('Enter the token symbol: '); break; - case 'Exit': + case 'EXIT': process.exit(); break; default: @@ -1178,13 +1564,13 @@ async function selectToken() { async function logTotalInvestors() { let investorsCount = await securityToken.methods.getInvestorCount().call(); - console.log(chalk.yellow(`Total investors at the moment: ${investorsCount}`)); + console.log(chalk.yellow(`Total investors at the moment: ${investorsCount} `)); } async function logBalance(from, totalSupply) { let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(from).call()); let percentage = totalSupply != '0' ? ` - ${parseFloat(fromBalance) / parseFloat(totalSupply) * 100}% of total supply` : ''; - console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol}${percentage}`)); + console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol} ${percentage} `)); } module.exports = { diff --git a/CLI/data/Transfer/LockupTM/add_lockup_data.csv b/CLI/data/Transfer/LockupTM/add_lockup_data.csv new file mode 100644 index 000000000..f3d27ab2d --- /dev/null +++ b/CLI/data/Transfer/LockupTM/add_lockup_data.csv @@ -0,0 +1,4 @@ +1000,1560178800,600,1,"TenMinutes" +1000,1560621600,3600,60,"OneHour" +2000,1567252800,7200,3600,"TwoHours" +3000,1567303200,14400,4800,"4Hours" diff --git a/CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv b/CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv new file mode 100644 index 000000000..68c08a7d7 --- /dev/null +++ b/CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv @@ -0,0 +1,12 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,"TenMinutes" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,"TenMinutes" +0xac297053173b02b02a737d47f7b4a718e5b170ef,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"TenMinutes" +0x10223927009b8add0960359dd90d1449415b7ca9,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,"OneHour" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,"OneHour" +0x56be93088141b16ebaa9416122fd1d928da25ecf,"OneHour" \ No newline at end of file diff --git a/CLI/data/Transfer/LockupTM/delete_lockup_data.csv b/CLI/data/Transfer/LockupTM/delete_lockup_data.csv new file mode 100644 index 000000000..0d9203ffe --- /dev/null +++ b/CLI/data/Transfer/LockupTM/delete_lockup_data.csv @@ -0,0 +1,2 @@ +"TwoHours" +"4Hours" \ No newline at end of file diff --git a/CLI/data/Transfer/LockupTM/modify_lockup_data.csv b/CLI/data/Transfer/LockupTM/modify_lockup_data.csv new file mode 100644 index 000000000..2b520c4ff --- /dev/null +++ b/CLI/data/Transfer/LockupTM/modify_lockup_data.csv @@ -0,0 +1,4 @@ +1000,1560178800,600,10,"TenMinutes" +1000,1560623200,3600,60,"OneHour" +2000,1567252800,7200,3600,"TwoHours" +6000,1567303200,14400,4800,"4Hours" diff --git a/CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv b/CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv new file mode 100644 index 000000000..68c08a7d7 --- /dev/null +++ b/CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv @@ -0,0 +1,12 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,"TenMinutes" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,"TenMinutes" +0xac297053173b02b02a737d47f7b4a718e5b170ef,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"TenMinutes" +0x10223927009b8add0960359dd90d1449415b7ca9,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,"OneHour" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,"OneHour" +0x56be93088141b16ebaa9416122fd1d928da25ecf,"OneHour" \ No newline at end of file From 4ae3a219c4f5880308201637da9a170f07d39f66 Mon Sep 17 00:00:00 2001 From: satyam Date: Wed, 12 Dec 2018 15:44:21 +0530 Subject: [PATCH 12/15] recommended fixes --- .../TransferManager/LockUpTransferManager.sol | 66 ++++++++----------- test/w_lockup_transfer_manager.js | 41 +++++------- 2 files changed, 46 insertions(+), 61 deletions(-) diff --git a/contracts/modules/TransferManager/LockUpTransferManager.sol b/contracts/modules/TransferManager/LockUpTransferManager.sol index 3d3450721..993bbd5bb 100644 --- a/contracts/modules/TransferManager/LockUpTransferManager.sol +++ b/contracts/modules/TransferManager/LockUpTransferManager.sol @@ -31,33 +31,33 @@ contract LockUpTransferManager is ITransferManager { bytes32[] lockupArray; - event AddNewLockUpToUser( - address indexed userAddress, - bytes32 indexed lockupName + event AddLockUpToUser( + address indexed _userAddress, + bytes32 indexed _lockupName ); event RemoveLockUpFromUser( - address indexed userAddress, - bytes32 indexed lockupName + address indexed _userAddress, + bytes32 indexed _lockupName ); event ModifyLockUpType( - uint256 lockupAmount, - uint256 startTime, - uint256 lockUpPeriodSeconds, - uint256 releaseFrequencySeconds, - bytes32 indexed lockupName + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 indexed _lockupName ); event AddNewLockUpType( - bytes32 indexed lockupName, - uint256 lockupAmount, - uint256 startTime, - uint256 lockUpPeriodSeconds, - uint256 releaseFrequencySeconds + bytes32 indexed _lockupName, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds ); - event RemoveLockUpType(bytes32 indexed lockupName); + event RemoveLockUpType(bytes32 indexed _lockupName); /** * @notice Constructor @@ -344,37 +344,27 @@ contract LockUpTransferManager is ITransferManager { } } - /** - * @notice Get the length of the lockups array for a specific user address - * @param _userAddress Address of the user whose tokens should be locked up - */ - function getLockUpsLength(address _userAddress) public view returns (uint256) { - return userToLockups[_userAddress].length; - } - /** * @notice Get a specific element in a user's lockups array given the user's address and the element index - * @param _userAddress Address of the user whose tokens should be locked up * @param _lockupName The name of the lockup */ - function getLockUp(address _userAddress, bytes32 _lockupName) public view returns ( + function getLockUp(bytes32 _lockupName) external view returns ( uint256 lockupAmount, uint256 startTime, uint256 lockUpPeriodSeconds, uint256 releaseFrequencySeconds, uint256 unlockedAmount ) { - require( - userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, - "User not assosicated with given lockup" - ); - return ( - lockups[_lockupName].lockupAmount, - lockups[_lockupName].startTime, - lockups[_lockupName].lockUpPeriodSeconds, - lockups[_lockupName].releaseFrequencySeconds, - _getUnlockedAmountForLockup(_lockupName) - ); + if (lockups[_lockupName].lockupAmount != 0) { + return ( + lockups[_lockupName].lockupAmount, + lockups[_lockupName].startTime, + lockups[_lockupName].lockUpPeriodSeconds, + lockups[_lockupName].releaseFrequencySeconds, + _getUnlockedAmountForLockup(_lockupName) + ); + } + return (uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)); } /** @@ -582,7 +572,7 @@ contract LockUpTransferManager is ITransferManager { lockupToUserIndex[_lockupName][_userAddress] = lockupToUsers[_lockupName].length; userToLockups[_userAddress].push(_lockupName); lockupToUsers[_lockupName].push(_userAddress); - emit AddNewLockUpToUser(_userAddress, _lockupName); + emit AddLockUpToUser(_userAddress, _lockupName); } function _addNewLockUpType( diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js index 59e1ed816..27924eb20 100644 --- a/test/w_lockup_transfer_manager.js +++ b/test/w_lockup_transfer_manager.js @@ -446,7 +446,7 @@ contract('LockUpTransferManager', accounts => { from: token_owner } ); - assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('12', 'ether')); + assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('12', 'ether')); }); it("Should fail to add the creation of the lockup where lockupName is already exists", async() => { @@ -478,8 +478,8 @@ contract('LockUpTransferManager', accounts => { from: token_owner } ); - assert.equal(tx.logs[1].args.userAddress, account_investor1); - assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('0.5', 'ether')); + assert.equal(tx.logs[1].args._userAddress, account_investor1); + assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('0.5', 'ether')); }); it("Should allow the creation of a lockup where the lockup amount is prime no", async() => { @@ -495,8 +495,8 @@ contract('LockUpTransferManager', accounts => { from: token_owner } ); - assert.equal(tx.logs[1].args.userAddress, account_investor1); - assert.equal((tx.logs[0].args.lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); + assert.equal(tx.logs[1].args._userAddress, account_investor1); + assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); }); it("Should prevent the transfer of tokens in a lockup", async() => { @@ -517,7 +517,7 @@ contract('LockUpTransferManager', accounts => { } ); await increaseTime(2); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) @@ -527,7 +527,7 @@ contract('LockUpTransferManager', accounts => { it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { // wait 20 seconds await increaseTime(duration.seconds(20)); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) @@ -535,13 +535,13 @@ contract('LockUpTransferManager', accounts => { }); it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); it("Should again transfer of tokens in a lockup if a period has passed", async() => { - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); }); @@ -550,7 +550,7 @@ contract('LockUpTransferManager', accounts => { // wait 20 more seconds + 1 to get rid of same block time await increaseTime(duration.seconds(21)); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor2, "b_lockup"); + let tx = await I_LockUpTransferManager.getLockUp.call( "b_lockup"); console.log("Amount get unlocked:", (tx[4].toNumber())); await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); }); @@ -682,8 +682,8 @@ contract('LockUpTransferManager', accounts => { ); await increaseTime(1); - let tx = await I_LockUpTransferManager.getLockUp.call(account_investor3, "c_lockup"); - let tx2 = await I_LockUpTransferManager.getLockUp.call(account_investor3, "d_lockup"); + let tx = await I_LockUpTransferManager.getLockUp.call("c_lockup"); + let tx2 = await I_LockUpTransferManager.getLockUp.call("d_lockup"); console.log("Total Amount get unlocked:", (tx[4].toNumber()) + (tx2[4].toNumber())); await catchRevert( I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor3 }) @@ -859,11 +859,7 @@ contract('LockUpTransferManager', accounts => { I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) ); - // check and get the lockup - let lockUpCount = await I_LockUpTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 1) - - let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, "f_lockup"); + let lockUp = await I_LockUpTransferManager.getLockUp("f_lockup"); console.log(lockUp); // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount assert.equal( @@ -902,7 +898,7 @@ contract('LockUpTransferManager', accounts => { it("Modify the lockup during the lockup periods", async() => { let balance = await I_SecurityToken.balanceOf(account_investor1) - let lockUp = await I_LockUpTransferManager.getLockUp(account_investor1, "f_lockup"); + let lockUp = await I_LockUpTransferManager.getLockUp("f_lockup"); console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); // edit the lockup await I_LockUpTransferManager.modifyLockUpType( @@ -931,7 +927,7 @@ contract('LockUpTransferManager', accounts => { // removing the lockup type let tx = await I_LockUpTransferManager.removeLockupType("k_lockup", {from: token_owner}); - assert.equal(web3.utils.toUtf8(tx.logs[0].args.lockupName), "k_lockup"); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._lockupName), "k_lockup"); // attaching the lockup to a user @@ -944,10 +940,9 @@ contract('LockUpTransferManager', accounts => { ); }) - it("Should succesfully get the lockup - fail because no lockup exist for the user of this name", async() => { - await catchRevert( - I_LockUpTransferManager.getLockUp(account_investor1, 9) - ); + it("Should succesfully get the non existed lockup value, it will give everything 0", async() => { + let data = await I_LockUpTransferManager.getLockUp(9); + assert.equal(data[0], 0); }) it("Should get configuration function signature", async() => { From d1598e5cffa9d6d9af8267193f66a968889cbfd1 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 12 Dec 2018 08:38:58 -0300 Subject: [PATCH 13/15] CLI - Fixes for new contract changes --- CLI/commands/transfer_manager.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 8fe30206e..8202e9b2c 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -906,14 +906,14 @@ async function lockUpTransferManager() { ); let addNewLockUpToUserReceipt = await common.sendTransaction(addNewLockUpToUserAction); let addNewLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddNewLockUpType'); - console.log(chalk.green(`${web3.utils.hexToUtf8(addNewLockUpToUserEvent.lockupName)} lockup type has been added successfully!`)); - let addLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddNewLockUpToUser'); - console.log(chalk.green(`${addLockUpToUserEvent.userAddress} has been added to ${web3.utils.hexToUtf8(addLockUpToUserEvent.lockupName)} successfully!`)); + console.log(chalk.green(`${web3.utils.hexToUtf8(addNewLockUpToUserEvent._lockupName)} lockup type has been added successfully!`)); + let addLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddLockUpToUser'); + console.log(chalk.green(`${addLockUpToUserEvent._userAddress} has been added to ${web3.utils.hexToUtf8(addLockUpToUserEvent._lockupName)} successfully!`)); } else { let addLockupTypeAction = currentTransferManager.methods.addNewLockUpType(web3.utils.toWei(lockupAmount.toString()), startTime, lockUpPeriodSeconds, releaseFrequencySeconds, web3.utils.toHex(name)); let addLockupTypeReceipt = await common.sendTransaction(addLockupTypeAction); let addLockupTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addLockupTypeReceipt.logs, 'AddNewLockUpType'); - console.log(chalk.green(`${web3.utils.hexToUtf8(addLockupTypeEvent.lockupName)} lockup type has been added successfully!`)); + console.log(chalk.green(`${web3.utils.hexToUtf8(addLockupTypeEvent._lockupName)} lockup type has been added successfully!`)); } break; case 'Manage existing lockups': @@ -955,13 +955,15 @@ async function manageExistingLockups(lockupName) { console.log('\n', chalk.blue(`Lockup ${web3.utils.hexToUtf8(lockupName)}`), '\n'); // Show current data - let currentLockup = await currentTransferManager.methods.lockups(lockupName).call(); + let currentLockup = await currentTransferManager.methods.getLockUp(lockupName).call(); let investors = await currentTransferManager.methods.getListOfAddresses(lockupName).call(); - console.log(`- Amount: ${web3.utils.fromWei(currentLockup.lockupAmount)} ${tokenSymbol}`) + + console.log(`- Amount: ${web3.utils.fromWei(currentLockup.lockupAmount)} ${tokenSymbol}`); + console.log(`- Currently unlocked: ${web3.utils.fromWei(currentLockup.unlockedAmount)} ${tokenSymbol}`); console.log(`- Start time: ${moment.unix(currentLockup.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); console.log(`- Lockup period: ${currentLockup.lockUpPeriodSeconds} seconds`); - console.log(`- End time: ${moment.unix(currentLockup.startTime).add(parseInt(currentLockup.lockUpPeriodSeconds)).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- End time: ${moment.unix(currentLockup.endTime).add(parseInt(currentLockup.lockUpPeriodSeconds)).format('MMMM Do YYYY, HH:mm:ss')}`); console.log(`- Release frequency: ${currentLockup.releaseFrequencySeconds} senconds`); console.log(`- Investors: ${investors.length}`); // ------------------ @@ -987,7 +989,7 @@ async function manageExistingLockups(lockupName) { let modifyLockUpTypeAction = currentTransferManager.methods.modifyLockUpType(lockupAmount, startTime, lockUpPeriodSeconds, releaseFrequencySeconds, lockupName); let modifyLockUpTypeReceipt = await common.sendTransaction(modifyLockUpTypeAction); let modifyLockUpTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyLockUpTypeReceipt.logs, 'ModifyLockUpType'); - console.log(chalk.green(`${web3.utils.hexToUtf8(modifyLockUpTypeEvent.lockupName)} lockup type has been modified successfully!`)); + console.log(chalk.green(`${web3.utils.hexToUtf8(modifyLockUpTypeEvent._lockupName)} lockup type has been modified successfully!`)); break; case 'Show investors': if (investors.length > 0) { @@ -1011,8 +1013,8 @@ async function manageExistingLockups(lockupName) { addInvestorToLockupAction = currentTransferManager.methods.addLockUpByNameMulti(investorsToAdd, investorsToAdd.map(i => lockupName)); } let addInvestorToLockupReceipt = await common.sendTransaction(addInvestorToLockupAction); - let addInvestorToLockupEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, addInvestorToLockupReceipt.logs, 'AddNewLockUpToUser'); - addInvestorToLockupEvents.map(e => console.log(chalk.green(`${e.userAddress} has been added to ${web3.utils.hexToUtf8(e.lockupName)} successfully!`))); + let addInvestorToLockupEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, addInvestorToLockupReceipt.logs, 'AddLockUpToUser'); + addInvestorToLockupEvents.map(e => console.log(chalk.green(`${e._userAddress} has been added to ${web3.utils.hexToUtf8(e._lockupName)} successfully!`))); break; case 'Remove this lockup from investors': let investorsToRemove = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { @@ -1029,7 +1031,7 @@ async function manageExistingLockups(lockupName) { } let removeLockUpFromUserReceipt = await common.sendTransaction(removeLockupFromInvestorAction); let removeLockUpFromUserEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, removeLockUpFromUserReceipt.logs, 'RemoveLockUpFromUser'); - removeLockUpFromUserEvents.map(e => console.log(chalk.green(`${e.userAddress} has been removed to ${web3.utils.hexToUtf8(e.lockupName)} successfully!`))); + removeLockUpFromUserEvents.map(e => console.log(chalk.green(`${e._userAddress} has been removed to ${web3.utils.hexToUtf8(e._lockupName)} successfully!`))); break; case 'Delete this lockup type': let isEmpty = investors.length === 0; @@ -1053,7 +1055,7 @@ async function manageExistingLockups(lockupName) { let removeLockupTypeAction = currentTransferManager.methods.removeLockupType(lockupName); let removeLockupTypeReceipt = await common.sendTransaction(removeLockupTypeAction); let removeLockupTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeLockupTypeReceipt.logs, 'RemoveLockUpType'); - console.log(chalk.green(`${web3.utils.hexToUtf8(removeLockupTypeEvent.lockupName)} lockup type has been deleted successfully!`)); + console.log(chalk.green(`${web3.utils.hexToUtf8(removeLockupTypeEvent._lockupName)} lockup type has been deleted successfully!`)); } return; case 'RETURN': From 23e7d51227ec0a7378096595cf24e503193c578a Mon Sep 17 00:00:00 2001 From: Mudit Gupta Date: Tue, 8 Jan 2019 19:28:48 +0530 Subject: [PATCH 14/15] Fixed test file --- test/z_fuzz_test_adding_removing_modules_ST.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/z_fuzz_test_adding_removing_modules_ST.js b/test/z_fuzz_test_adding_removing_modules_ST.js index 32d28c6ab..5b6c8b57d 100644 --- a/test/z_fuzz_test_adding_removing_modules_ST.js +++ b/test/z_fuzz_test_adding_removing_modules_ST.js @@ -20,7 +20,7 @@ const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager') // modules for test const CountTransferManager = artifacts.require("./CountTransferManager"); const ManualApprovalTransferManager = artifacts.require('./ManualApprovalTransferManager'); -const VolumeRestrictionTransferManager = artifacts.require('./LockupVolumeRestrictionTM'); +const VolumeRestrictionTransferManager = artifacts.require('./LockUpTransferManager'); const PercentageTransferManager = artifacts.require('./PercentageTransferManager'); From 887860e3e5115e470aa9d4a0d429bc8b4cf3f91b Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 10:54:11 -0300 Subject: [PATCH 15/15] Trigger TravisCI