From 3eb609af8d74dfb6bc78125afd62ce715b6858e0 Mon Sep 17 00:00:00 2001 From: satyam Date: Tue, 8 Jan 2019 15:53:00 +0530 Subject: [PATCH 01/17] add getRestrictedAddresses() function --- .../TransferManager/VolumeRestrictionTM.sol | 53 +++++++++++++++++++ .../VolumeRestrictionTMStorage.sol | 15 +++++- test/y_volume_restriction_tm.js | 43 ++++++++++++++- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index 44534351d..70ee114d0 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -205,6 +205,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _endTime, RestrictionType(_restrictionType) ); + _addRestrictionData(_holder, uint8(TypeOfPeriod.MultipleDays)); emit AddIndividualRestriction( _holder, _allowedTokens, @@ -215,6 +216,16 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ); } + function _addRestrictionData(address _holder, uint8 _callFrom) internal { + uint64 index = restrictedHolders[_holder].index; + if (restrictedHolders[_holder].seen == 0) { + restrictedAddresses.push(_holder); + index = uint64(restrictedAddresses.length); + } + uint8 _type = _getTypeOfPeriod(restrictedHolders[_holder].typeOfPeriod, _callFrom, _holder); + restrictedHolders[_holder] = RestrictedHolder(uint8(1), _type, index); + } + /** * @notice Use to add the new individual daily restriction for all token holder * @param _holder Address of the token holder, whom restriction will be implied @@ -270,6 +281,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _endTime, RestrictionType(_restrictionType) ); + _addRestrictionData(_holder, uint8(TypeOfPeriod.OneDay)); emit AddIndividualDailyRestriction( _holder, _allowedTokens, @@ -449,12 +461,28 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { require(_holder != address(0), "Invalid address"); require(individualRestriction[_holder].endTime != 0, "Not present"); individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + _deleteHolderFromList(_holder, uint8(TypeOfPeriod.OneDay)); userToBucket[_holder].lastTradedDayTime = 0; userToBucket[_holder].sumOfLastPeriod = 0; userToBucket[_holder].daysCovered = 0; emit IndividualRestrictionRemoved(_holder); } + function _deleteHolderFromList(address _holder, uint8 _typeOfPeriod) internal { + if (restrictedHolders[_holder].typeOfPeriod != uint8(TypeOfPeriod.Both)) { + uint64 index = restrictedHolders[_holder].index; + uint256 _len = restrictedAddresses.length; + if (index != _len) { + restrictedHolders[restrictedAddresses[_len - 1]].index = index; + restrictedAddresses[index - 1] = restrictedAddresses[_len - 1]; + } + delete restrictedHolders[_holder]; + restrictedAddresses.length--; + } else { + restrictedHolders[_holder].typeOfPeriod = _typeOfPeriod; + } + } + /** * @notice use to remove the individual restriction for a given address * @param _holders Array of address of the user @@ -478,6 +506,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { require(_holder != address(0), "Invalid address"); require(individualDailyRestriction[_holder].endTime != 0, "Not present"); individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + _deleteHolderFromList(_holder, uint8(TypeOfPeriod.MultipleDays)); userToBucket[_holder].dailyLastTradedDayTime = 0; emit IndividualDailyRestrictionRemoved(_holder); } @@ -1153,6 +1182,28 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { return bytes4(0); } + function _getTypeOfPeriod(uint8 _currentTypeOfPeriod, uint8 _callFrom, address _holder) internal view returns(uint8) { + if (_currentTypeOfPeriod != _callFrom && individualRestriction[_holder].endTime != uint256(0)) + return uint8(TypeOfPeriod.Both); + else + return _callFrom; + } + + /** + * @notice use to get the list of token holders who are restricted by the VRTM + * @return address List of addresses that are restricted by the VRTM + * @return uint8 Array of the Type of period restriction on the addresses. 0 - address + * has only individual restriction, 1 - address has only individual daily restriction & 2 + * it means address has both type of restriction where rolling period is 24 hrs & multiple days as well + */ + function getRestrictedAddresses() external view returns(address[], uint8[]) { + uint8[] memory typeOfPeriodRestriction = new uint8[](restrictedAddresses.length); + for (uint256 i = 0; i < restrictedAddresses.length; i++) { + typeOfPeriodRestriction[i] = restrictedHolders[restrictedAddresses[i]].typeOfPeriod; + } + return (restrictedAddresses, typeOfPeriodRestriction); + } + /** * @notice Returns the permissions flag that are associated with Percentage transfer Manager */ @@ -1162,4 +1213,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { return allPermissions; } + + } \ No newline at end of file diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol index e995676a0..4e5d3512d 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol @@ -7,6 +7,8 @@ contract VolumeRestrictionTMStorage { enum RestrictionType { Fixed, Percentage } + enum TypeOfPeriod { MultipleDays, OneDay, Both } + struct VolumeRestriction { // If typeOfRestriction is `Percentage` then allowedTokens will be in // the % (w.r.t to totalSupply) with a multiplier of 10**16 . else it @@ -25,6 +27,15 @@ contract VolumeRestrictionTMStorage { uint256 dailyLastTradedDayTime; } + struct RestrictedHolder { + // 1 represent true & 0 for false + uint8 seen; + // Type of period will be enum index of TypeOfPeriod enum + uint8 typeOfPeriod; + // Index of the array where the holder address lives + uint64 index; + } + // Global restriction that applies to all token holders VolumeRestriction public defaultRestriction; // Daily global restriction that applies to all token holders (Total ST traded daily is restricted) @@ -41,5 +52,7 @@ contract VolumeRestrictionTMStorage { mapping(address => BucketDetails) internal defaultUserToBucket; // List of wallets that are exempted from all the restrictions applied by the this contract mapping(address => bool) public exemptList; - + mapping(address => RestrictedHolder) internal restrictedHolders; + address[] public restrictedAddresses; + } \ No newline at end of file diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index 1075a64e4..004b58fa7 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -409,6 +409,10 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal(tx.logs[0].args._typeOfRestriction, 0); + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0][0], account_investor1); + assert.equal(data[1][0].toNumber(), 0); + }); it("Should add the restriction for multiple investor -- failed because of bad owner", async() => { @@ -538,6 +542,9 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[2].toNumber(), 3); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3))[2].toNumber(), 4); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor4))[2].toNumber(), 5); + + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 4); }); it("Should remove the restriction multi -- failed because of address is 0", async() => { @@ -554,6 +561,12 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should successfully remove the restriction", async() => { await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 3); + for (let i = 0; i < data.length; i++) { + assert.notEqual(data[0][i], account_investor2); + assert.equal(data[1][i], 0); + } }); it("Should remove the restriction -- failed because restriction not present anymore", async() => { @@ -569,6 +582,8 @@ contract('VolumeRestrictionTransferManager', accounts => { from: token_owner } ) + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 1); }); it("Should add the restriction succesfully after the expiry of previous one for investor 1", async() => { @@ -603,6 +618,9 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[1].args._holder, account_investor1); assert.equal(tx.logs[1].args._typeOfRestriction, 0); + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 1); + assert.equal(data[0][0], account_investor1); }); it("Should not successfully transact the tokens -- failed because volume is above the limit", async() => { @@ -717,6 +735,10 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 0); assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("6")); + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 2); + assert.equal(data[0][1], account_investor3); + assert.equal(data[1][1], 1); let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); console.log(` *** Individual Daily restriction data *** @@ -793,6 +815,12 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal((tx.logs[0].args._typeOfRestriction).toNumber(), 1); assert.equal((tx.logs[0].args._allowedTokens).dividedBy(new BigNumber(10).pow(16)).toNumber(), 5); + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 2); + assert.equal(data[0][1], account_investor3); + assert.equal(data[0][0], account_investor1); + assert.equal(data[1][1].toNumber(), 1); + assert.equal(data[1][0].toNumber(), 2); let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1); console.log(` *** Individual Daily restriction data *** @@ -857,6 +885,13 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 1); + + let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(data[0].length, 2); + assert.equal(data[0][1], account_investor3); + assert.equal(data[0][0], account_investor1); + assert.equal(data[1][1].toNumber(), 2); + assert.equal(data[1][0].toNumber(), 2); }); it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async() => { @@ -910,7 +945,13 @@ contract('VolumeRestrictionTransferManager', accounts => { // remove the Individual daily restriction let tx = await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: token_owner}); assert.equal(tx.logs[0].args._holder, account_investor3); - + let dataAdd = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + assert.equal(dataAdd[0].length, 2); + assert.equal(dataAdd[0][0], account_investor1); + assert.equal(dataAdd[1][0].toNumber(), 2); + assert.equal(dataAdd[0][1], account_investor3); + assert.equal(dataAdd[1][1].toNumber(), 0); + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); // transfer more tokens on the same day From d85c5947176f1fe31378d95adeaec35506080c1e Mon Sep 17 00:00:00 2001 From: satyam Date: Tue, 8 Jan 2019 18:02:41 +0530 Subject: [PATCH 02/17] minor fix --- .../TransferManager/VolumeRestrictionTM.sol | 18 ++++++++++++------ .../VolumeRestrictionTMStorage.sol | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index 70ee114d0..50c63624f 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -217,10 +217,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } function _addRestrictionData(address _holder, uint8 _callFrom) internal { - uint64 index = restrictedHolders[_holder].index; + uint128 index = restrictedHolders[_holder].index; if (restrictedHolders[_holder].seen == 0) { restrictedAddresses.push(_holder); - index = uint64(restrictedAddresses.length); + index = uint128(restrictedAddresses.length); } uint8 _type = _getTypeOfPeriod(restrictedHolders[_holder].typeOfPeriod, _callFrom, _holder); restrictedHolders[_holder] = RestrictedHolder(uint8(1), _type, index); @@ -469,8 +469,14 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } function _deleteHolderFromList(address _holder, uint8 _typeOfPeriod) internal { + // Deleting the holder if holder's type of Period is `Both` type otherwise + // it will assign the given type `_typeOfPeriod` to the _holder typeOfPeriod + // `_typeOfPeriod` it always be contrary to the removing restriction + // if removing restriction is individual then typeOfPeriod is TypeOfPeriod.OneDay + // in uint8 its value is 1. if removing restriction is daily individual then typeOfPeriod + // is TypeOfPeriod.MultipleDays in uint8 its value is 0. if (restrictedHolders[_holder].typeOfPeriod != uint8(TypeOfPeriod.Both)) { - uint64 index = restrictedHolders[_holder].index; + uint128 index = restrictedHolders[_holder].index; uint256 _len = restrictedAddresses.length; if (index != _len) { restrictedHolders[restrictedAddresses[_len - 1]].index = index; @@ -694,7 +700,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _startTimes.length == _holders.length && _holders.length == _endTimes.length && _endTimes.length == _restrictionTypes.length, - "Array length mismatch" + "Length mismatch" ); require(_holders.length == _allowedTokens.length, "Length mismatch"); for (uint256 i = 0; i < _holders.length; i++) { @@ -1110,7 +1116,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { require(_endTime > _startTime, "Invalid times"); // Maximum limit for the rollingPeriod is 365 days require(_rollingPeriodDays >= 1 && _rollingPeriodDays <= 365, "Invalid rollingperiod"); - require(BokkyPooBahsDateTimeLibrary.diffDays(_startTime, _endTime) >= _rollingPeriodDays, "Invalid start & end time"); + require(BokkyPooBahsDateTimeLibrary.diffDays(_startTime, _endTime) >= _rollingPeriodDays, "Invalid times"); } function _checkLengthOfArray( @@ -1128,7 +1134,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _startTimes.length == _rollingPeriodInDays.length && _rollingPeriodInDays.length == _endTimes.length && _endTimes.length == _restrictionTypes.length, - "Array length mismatch" + "Length mismatch" ); } diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol index 4e5d3512d..ddc2b8e21 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol @@ -33,7 +33,7 @@ contract VolumeRestrictionTMStorage { // Type of period will be enum index of TypeOfPeriod enum uint8 typeOfPeriod; // Index of the array where the holder address lives - uint64 index; + uint128 index; } // Global restriction that applies to all token holders From f5992b42009f15bf9e7d0d160793b51b570cba27 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 8 Jan 2019 15:54:41 -0400 Subject: [PATCH 03/17] Add restrictions data to getRestrictedAddresses --- .../TransferManager/VolumeRestrictionTM.sol | 281 ++++++++++-------- test/y_volume_restriction_tm.js | 57 ++-- 2 files changed, 183 insertions(+), 155 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index 50c63624f..eb9475360 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -6,7 +6,7 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../../libraries/BokkyPooBahsDateTimeLibrary.sol"; contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { - + using SafeMath for uint256; // permission definition @@ -115,25 +115,25 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { if (!paused && _from != address(0) && !exemptList[_from]) { // Function must only be called by the associated security token if _isTransfer == true require(msg.sender == securityToken || !_isTransfer); - // Checking the individual restriction if the `_from` comes in the individual category - if ((individualRestriction[_from].endTime >= now && individualRestriction[_from].startTime <= now) + // Checking the individual restriction if the `_from` comes in the individual category + if ((individualRestriction[_from].endTime >= now && individualRestriction[_from].startTime <= now) || (individualDailyRestriction[_from].endTime >= now && individualDailyRestriction[_from].startTime <= now)) { return _individualRestrictionCheck(_from, _amount, _isTransfer); // If the `_from` doesn't fall under the individual category. It will processed with in the global category automatically } else if ((defaultRestriction.endTime >= now && defaultRestriction.startTime <= now) || (defaultDailyRestriction.endTime >= now && defaultDailyRestriction.startTime <= now)) { - + return _defaultRestrictionCheck(_from, _amount, _isTransfer); } - } + } return Result.NA; } /** * @notice Add/Remove wallet address from the exempt list * @param _wallet Ethereum wallet/contract address that need to be exempted - * @param _change Boolean value used to add (i.e true) or remove (i.e false) from the list + * @param _change Boolean value used to add (i.e true) or remove (i.e false) from the list */ function changeExemptWalletList(address _wallet, bool _change) public withPerm(ADMIN) { require(_wallet != address(0), "Invalid address"); @@ -156,7 +156,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _startTime, uint256 _rollingPeriodInDays, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) external withPerm(ADMIN) @@ -178,29 +178,29 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _startTime, uint256 _rollingPeriodInDays, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) internal - { - - uint256 startTime = _startTime; + { + + /* uint256 startTime = _startTime; */ if (_startTime == 0) { - startTime = now; + _startTime = now; } require( individualRestriction[_holder].endTime < now, - "Already present" + "Not Allowed" ); require(_holder != address(0) && !exemptList[_holder], "Invalid address"); - require(startTime >= now, "Invalid startTime"); - _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); - + /* require(_startTime >= now, "Invalid startTime"); */ + _checkInputParams(_allowedTokens, _startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); + if (individualRestriction[_holder].endTime != 0) { _removeIndividualRestriction(_holder); } individualRestriction[_holder] = VolumeRestriction( _allowedTokens, - startTime, + _startTime, _rollingPeriodInDays, _endTime, RestrictionType(_restrictionType) @@ -209,7 +209,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { emit AddIndividualRestriction( _holder, _allowedTokens, - startTime, + _startTime, _rollingPeriodInDays, _endTime, _restrictionType @@ -239,11 +239,11 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _allowedTokens, uint256 _startTime, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) external withPerm(ADMIN) - { + { _addIndividualDailyRestriction( _holder, _allowedTokens, @@ -259,24 +259,24 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _allowedTokens, uint256 _startTime, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) internal - { + { - uint256 startTime = _startTime; + /* uint256 startTime = _startTime; */ if (_startTime == 0) { - startTime = now; + _startTime = now; } require( individualDailyRestriction[_holder].endTime < now, "Not Allowed" ); - require(startTime >= now, "Invalid startTime"); - _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType); + /* require(startTime >= now, "Invalid startTime"); */ + _checkInputParams(_allowedTokens, _startTime, 1, _endTime, _restrictionType, now); individualDailyRestriction[_holder] = VolumeRestriction( _allowedTokens, - startTime, + _startTime, 1, _endTime, RestrictionType(_restrictionType) @@ -285,7 +285,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { emit AddIndividualDailyRestriction( _holder, _allowedTokens, - startTime, + _startTime, 1, _endTime, _restrictionType @@ -305,25 +305,26 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] _allowedTokens, uint256[] _startTimes, uint256[] _endTimes, - uint256[] _restrictionTypes + uint256[] _restrictionTypes ) public withPerm(ADMIN) { - require( + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); + /* require( _allowedTokens.length == _startTimes.length && _startTimes.length == _holders.length && _holders.length == _endTimes.length && _endTimes.length == _restrictionTypes.length, "Array length mismatch" - ); + ); */ for (uint256 i = 0; i < _holders.length; i++) { _addIndividualDailyRestriction( _holders[i], _allowedTokens[i], _startTimes[i], _endTimes[i], - _restrictionTypes[i] + _restrictionTypes[i] ); } } @@ -343,13 +344,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] _startTimes, uint256[] _rollingPeriodInDays, uint256[] _endTimes, - uint256[] _restrictionTypes + uint256[] _restrictionTypes ) public withPerm(ADMIN) { - _checkLengthOfArray(_allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); - require(_holders.length == _allowedTokens.length, "Length mismatch"); + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + /* require(_holders.length == _allowedTokens.length, "Length mismatch"); */ for (uint256 i = 0; i < _holders.length; i++) { _addIndividualRestriction( _holders[i], @@ -357,7 +358,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _startTimes[i], _rollingPeriodInDays[i], _endTimes[i], - _restrictionTypes[i] + _restrictionTypes[i] ); } } @@ -375,21 +376,21 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _startTime, uint256 _rollingPeriodInDays, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) external withPerm(ADMIN) - { + { uint256 startTime = _startTime; if (_startTime == 0) { startTime = now; } require( defaultRestriction.endTime < now, - "Not allowed" + "Not Allowed" ); - require(startTime >= now, "Invalid startTime"); - _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); + /* require(startTime >= now, "Invalid startTime"); */ + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); defaultRestriction = VolumeRestriction( _allowedTokens, startTime, @@ -417,11 +418,11 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _allowedTokens, uint256 _startTime, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) external withPerm(ADMIN) - { + { uint256 startTime = _startTime; if (_startTime == 0) { startTime = now; @@ -430,8 +431,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { defaultDailyRestriction.endTime < now, "Not Allowed" ); - require(startTime >= now, "Invalid startTime"); - _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType); + /* require(startTime >= now, "Invalid startTime"); */ + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, now); defaultDailyRestriction = VolumeRestriction( _allowedTokens, startTime, @@ -450,7 +451,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice use to remove the individual restriction for a given address - * @param _holder Address of the user + * @param _holder Address of the user */ function removeIndividualRestriction(address _holder) external withPerm(ADMIN) { _removeIndividualRestriction(_holder); @@ -491,7 +492,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice use to remove the individual restriction for a given address - * @param _holders Array of address of the user + * @param _holders Array of address of the user */ function removeIndividualRestrictionMulti(address[] _holders) external withPerm(ADMIN) { for (uint256 i = 0; i < _holders.length; i++) { @@ -501,7 +502,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice use to remove the individual daily restriction for a given address - * @param _holder Address of the user + * @param _holder Address of the user */ function removeIndividualDailyRestriction(address _holder) external withPerm(ADMIN) { _removeIndividualDailyRestriction(_holder); @@ -519,7 +520,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice use to remove the individual daily restriction for a given address - * @param _holders Array of address of the user + * @param _holders Array of address of the user */ function removeIndividualDailyRestrictionMulti(address[] _holders) external withPerm(ADMIN) { for (uint256 i = 0; i < _holders.length; i++) { @@ -543,7 +544,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { require(defaultDailyRestriction.endTime != 0); defaultDailyRestriction = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); emit DefaultDailyRestrictionRemoved(); - } + } /** * @notice Use to modify the existing individual restriction for a given token holder @@ -560,7 +561,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _startTime, uint256 _rollingPeriodInDays, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) external withPerm(ADMIN) @@ -582,20 +583,20 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _startTime, uint256 _rollingPeriodInDays, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) internal - { - uint256 startTime = _startTime; + { + /* uint256 startTime = _startTime; */ if (_startTime == 0) { - startTime = now; + _startTime = now; } - require(individualRestriction[_holder].startTime > now, "Not allowed"); - require(startTime >= now, "Invalid startTime"); - _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); + require(individualRestriction[_holder].startTime > now, "Not Allowed"); + /* require(startTime >= now, "Invalid startTime"); */ + _checkInputParams(_allowedTokens, _startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); individualRestriction[_holder] = VolumeRestriction( _allowedTokens, - startTime, + _startTime, _rollingPeriodInDays, _endTime, RestrictionType(_restrictionType) @@ -603,7 +604,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { emit ModifyIndividualRestriction( _holder, _allowedTokens, - startTime, + _startTime, _rollingPeriodInDays, _endTime, _restrictionType @@ -625,7 +626,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _allowedTokens, uint256 _startTime, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) external withPerm(ADMIN) @@ -645,24 +646,26 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _allowedTokens, uint256 _startTime, uint256 _endTime, - uint256 _restrictionType + uint256 _restrictionType ) internal - { - uint256 startTime = _startTime; + { + /* uint256 startTime = _startTime; */ if (_startTime == 0) { - startTime = now; + _startTime = now; } - _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType); - // If old startTime is already passed then new startTime should be greater than or equal to the + _checkInputParams(_allowedTokens, _startTime, 1, _endTime, _restrictionType, + (individualDailyRestriction[_holder].startTime <= now ? individualDailyRestriction[_holder].startTime : now) + ); + // If old startTime is already passed then new startTime should be greater than or equal to the // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. - if (individualDailyRestriction[_holder].startTime <= now) + /* if (individualDailyRestriction[_holder].startTime <= now) require(startTime >= individualDailyRestriction[_holder].startTime, "Invalid StartTime"); - else - require(startTime >= now); + else + require(startTime >= now); */ individualDailyRestriction[_holder] = VolumeRestriction( _allowedTokens, - startTime, + _startTime, 1, _endTime, RestrictionType(_restrictionType) @@ -670,7 +673,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { emit ModifyIndividualDailyRestriction( _holder, _allowedTokens, - startTime, + _startTime, 1, _endTime, _restrictionType @@ -690,7 +693,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] _allowedTokens, uint256[] _startTimes, uint256[] _endTimes, - uint256[] _restrictionTypes + uint256[] _restrictionTypes ) public withPerm(ADMIN) @@ -709,10 +712,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _allowedTokens[i], _startTimes[i], _endTimes[i], - _restrictionTypes[i] + _restrictionTypes[i] ); } - } + } /** * @notice Use to modify the existing individual restriction for multiple token holders @@ -729,13 +732,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] _startTimes, uint256[] _rollingPeriodInDays, uint256[] _endTimes, - uint256[] _restrictionTypes + uint256[] _restrictionTypes ) public withPerm(ADMIN) { - _checkLengthOfArray(_allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); - require(_holders.length == _allowedTokens.length, "Length mismatch"); + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + /* require(_holders.length == _allowedTokens.length, "Length mismatch"); */ for (uint256 i = 0; i < _holders.length; i++) { _modifyIndividualRestriction( _holders[i], @@ -743,10 +746,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _startTimes[i], _rollingPeriodInDays[i], _endTimes[i], - _restrictionTypes[i] + _restrictionTypes[i] ); } - } + } /** * @notice Use to modify the global restriction for all token holder @@ -765,14 +768,14 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ) external withPerm(ADMIN) - { - require(defaultRestriction.startTime > now, "Not allowed"); + { + require(defaultRestriction.startTime > now, "Not Allowed"); uint256 startTime = _startTime; if (_startTime == 0) { startTime = now; } - require(startTime >= now, "Invalid startTime"); - _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); + /* require(startTime >= now, "Invalid startTime"); */ + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); defaultRestriction = VolumeRestriction( _allowedTokens, startTime, @@ -806,18 +809,20 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ) external withPerm(ADMIN) - { + { uint256 startTime = _startTime; if (_startTime == 0) { startTime = now; } - _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType); - // If old startTime is already passed then new startTime should be greater than or equal to the + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, + (defaultDailyRestriction.startTime <= now ? defaultDailyRestriction.startTime : now) + ); + // If old startTime is already passed then new startTime should be greater than or equal to the // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. - if (defaultDailyRestriction.startTime <= now) + /* if (defaultDailyRestriction.startTime <= now) require(startTime >= defaultDailyRestriction.startTime, "Invalid StartTime"); - else - require(startTime >= now); + else + require(startTime >= now); */ defaultDailyRestriction = VolumeRestriction( _allowedTokens, startTime, @@ -838,7 +843,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice Internal function used to validate the transaction for a given address * If it validates then it also update the storage corressponds to the default restriction */ - function _defaultRestrictionCheck(address _from, uint256 _amount, bool _isTransfer) internal returns (Result) { + function _defaultRestrictionCheck(address _from, uint256 _amount, bool _isTransfer) internal returns (Result) { // using the variable to avoid stack too deep error BucketDetails memory bucketDetails = defaultUserToBucket[_from]; uint256 daysCovered = defaultRestriction.rollingPeriodInDays; @@ -855,7 +860,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { // Picking up the last timestamp fromTimestamp = bucketDetails.lastTradedDayTime; } - + // Check with the bucket and parse all the new timestamps to calculate the sumOfLastPeriod // re-using the local variables to avoid the stack too deep error. (sumOfLastPeriod, fromTimestamp, daysCovered) = _bucketCheck( @@ -871,7 +876,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } } (allowedDaily, dailyTime) = _dailyTxCheck(_from, _amount, bucketDetails.dailyLastTradedDayTime, defaultDailyRestriction); - + if (_isTransfer) { _updateStorage( _from, @@ -891,7 +896,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @notice Internal function used to validate the transaction for a given address * If it validates then it also update the storage corressponds to the individual restriction */ - function _individualRestrictionCheck(address _from, uint256 _amount, bool _isTransfer) internal returns (Result) { + function _individualRestrictionCheck(address _from, uint256 _amount, bool _isTransfer) internal returns (Result) { // using the variable to avoid stack too deep error BucketDetails memory bucketDetails = userToBucket[_from]; VolumeRestriction memory dailyRestriction = individualDailyRestriction[_from]; @@ -910,7 +915,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { // Picking up the last timestamp fromTimestamp = bucketDetails.lastTradedDayTime; } - + // Check with the bucket and parse all the new timestamps to calculate the sumOfLastPeriod // re-using the local variables to avoid the stack too deep error. (sumOfLastPeriod, fromTimestamp, daysCovered) = _bucketCheck( @@ -926,7 +931,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } } (allowedDaily, dailyTime) = _dailyTxCheck(_from, _amount, bucketDetails.dailyLastTradedDayTime, dailyRestriction); - + if (_isTransfer) { _updateStorage( _from, @@ -948,14 +953,14 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 amount, uint256 dailyLastTradedDayTime, VolumeRestriction restriction - ) + ) internal view returns(bool, uint256) { // Checking whether the daily restriction is added or not if yes then calculate // the total amount get traded on a particular day (~ _fromTime) - if ( now <= restriction.endTime && now >= restriction.startTime) { + if ( now <= restriction.endTime && now >= restriction.startTime) { uint256 txSumOfDay = 0; if (dailyLastTradedDayTime == 0 || dailyLastTradedDayTime < restriction.startTime) // This if condition will be executed when the individual daily restriction executed first time @@ -997,7 +1002,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { if (counter >= _rollingPeriodInDays) { // Subtracting the former value(Sum of all the txn amount of that day) from the sumOfLastPeriod // The below line subtracts (the traded volume on days no longer covered by rolling period) from sumOfLastPeriod. - // Every loop execution subtracts one day's trade volume. + // Every loop execution subtracts one day's trade volume. // Loop starts from the first day covered in sumOfLastPeriod upto the day that is covered by rolling period. uint256 temp = _bucketDetails.daysCovered.sub(counter.sub(_rollingPeriodInDays)); temp = _bucketDetails.lastTradedDayTime.sub(temp.mul(1 days)); @@ -1009,7 +1014,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } } // calculating the timestamp that will used as an index of the next bucket - // i.e buckets period will be look like this T1 to T2-1, T2 to T3-1 .... + // i.e buckets period will be look like this T1 to T2-1, T2 to T3-1 .... // where T1,T2,T3 are timestamps having 24 hrs difference _fromTime = _fromTime.add(_diffDays.mul(1 days)); return (sumOfLastPeriod, _fromTime, counter); @@ -1019,11 +1024,11 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _sumOfLastPeriod, uint256 _amountToTransact, VolumeRestriction _restriction - ) + ) internal view returns (bool) - { + { uint256 _allowedAmount = 0; if (_restriction.typeOfRestriction == RestrictionType.Percentage) { _allowedAmount = (_restriction.allowedTokens.mul(ISecurityToken(securityToken).totalSupply())) / uint256(10) ** 18; @@ -1039,14 +1044,14 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _amount, uint256 _lastTradedDayTime, uint256 _sumOfLastPeriod, - uint256 _daysCovered, + uint256 _daysCovered, uint256 _dailyLastTradedDayTime, uint256 _endTime, bool isDefault ) - internal - { - + internal + { + if (isDefault){ BucketDetails storage defaultUserToBucketDetails = defaultUserToBucket[_from]; _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, _endTime, defaultUserToBucketDetails); @@ -1062,13 +1067,13 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 _amount, uint256 _lastTradedDayTime, uint256 _sumOfLastPeriod, - uint256 _daysCovered, + uint256 _daysCovered, uint256 _dailyLastTradedDayTime, uint256 _endTime, BucketDetails storage details ) - internal - { + internal + { // Cheap storage technique if (details.lastTradedDayTime != _lastTradedDayTime) { // Assigning the latest transaction timestamp of the day @@ -1091,20 +1096,22 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { // Increasing the total amount of the day by `_amount` bucket[_from][_dailyLastTradedDayTime] = bucket[_from][_dailyLastTradedDayTime].add(_amount); } - } + } } function _checkInputParams( uint256 _allowedTokens, - uint256 _startTime, - uint256 _rollingPeriodDays, + uint256 _startTime, + uint256 _rollingPeriodDays, uint256 _endTime, - uint256 _restrictionType - ) + uint256 _restrictionType, + uint256 _earliestStartTime + ) internal pure { require(_restrictionType == 0 || _restrictionType == 1, "Invalid type"); + require(_startTime >= _earliestStartTime, "Invalid startTime"); if (_restrictionType == uint256(RestrictionType.Fixed)) { require(_allowedTokens > 0, "Invalid value"); } else { @@ -1117,9 +1124,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { // Maximum limit for the rollingPeriod is 365 days require(_rollingPeriodDays >= 1 && _rollingPeriodDays <= 365, "Invalid rollingperiod"); require(BokkyPooBahsDateTimeLibrary.diffDays(_startTime, _endTime) >= _rollingPeriodDays, "Invalid times"); - } + } function _checkLengthOfArray( + address[] _holders, uint256[] _allowedTokens, uint256[] _startTimes, uint256[] _rollingPeriodInDays, @@ -1127,9 +1135,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] _restrictionTypes ) internal - pure + pure { require( + _holders.length == _allowedTokens.length && _allowedTokens.length == _startTimes.length && _startTimes.length == _rollingPeriodInDays.length && _rollingPeriodInDays.length == _endTimes.length && @@ -1146,7 +1155,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256 days covered * @return uint256 24h lastTradedDayTime */ - function getIndividualBucketDetailsToUser(address _user) external view returns(uint256, uint256, uint256, uint256) { + function getIndividualBucketDetailsToUser(address _user) public view returns(uint256, uint256, uint256, uint256) { return( userToBucket[_user].lastTradedDayTime, userToBucket[_user].sumOfLastPeriod, @@ -1163,7 +1172,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256 days covered * @return uint256 24h lastTradedDayTime */ - function getDefaultBucketDetailsToUser(address _user) external view returns(uint256, uint256, uint256, uint256) { + function getDefaultBucketDetailsToUser(address _user) public view returns(uint256, uint256, uint256, uint256) { return( defaultUserToBucket[_user].lastTradedDayTime, defaultUserToBucket[_user].sumOfLastPeriod, @@ -1175,7 +1184,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice Use to get the volume of token that being traded at a particular day (`_at` + 24 hours) for a given user * @param _user Address of the token holder - * @param _at Timestamp + * @param _at Timestamp */ function getTotalTradedByUser(address _user, uint256 _at) external view returns(uint256) { return bucket[_user][_at]; @@ -1197,17 +1206,37 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /** * @notice use to get the list of token holders who are restricted by the VRTM - * @return address List of addresses that are restricted by the VRTM - * @return uint8 Array of the Type of period restriction on the addresses. 0 - address + * @return address[] List of addresses that are restricted by the VRTM + * @return uint8[] Array of the Type of period restriction on the addresses. 0 - address * has only individual restriction, 1 - address has only individual daily restriction & 2 * it means address has both type of restriction where rolling period is 24 hrs & multiple days as well + * @return uint256[] List of lastTradedDayTime, first group for individuals, second group for default + * @return uint256[] List of sumOfLastPeriod, first group for individuals, second group for default + * @return uint256[] List of daysCovered, first group for individuals, second group for default + * @return uint256[] List of dailyLastTradedDayTime, first group for individuals, second group for default */ - function getRestrictedAddresses() external view returns(address[], uint8[]) { - uint8[] memory typeOfPeriodRestriction = new uint8[](restrictedAddresses.length); + function getRestrictedAddresses() external view returns( + address[] memory allAddresses, + uint8[] memory typeOfPeriodRestriction, + uint256[] memory lastTradedDayTime, + uint256[] memory sumOfLastPeriod, + uint256[] memory daysCovered, + uint256[] memory dailyLastTradedDayTime + ) { + allAddresses = restrictedAddresses; + typeOfPeriodRestriction = new uint8[](restrictedAddresses.length); + lastTradedDayTime = new uint256[](2 * restrictedAddresses.length); + sumOfLastPeriod = new uint256[](2 * restrictedAddresses.length); + daysCovered = new uint256[](2 * restrictedAddresses.length); + dailyLastTradedDayTime = new uint256[](2 * restrictedAddresses.length); for (uint256 i = 0; i < restrictedAddresses.length; i++) { typeOfPeriodRestriction[i] = restrictedHolders[restrictedAddresses[i]].typeOfPeriod; + (lastTradedDayTime[i], sumOfLastPeriod[i], daysCovered[i], dailyLastTradedDayTime[i]) = + getIndividualBucketDetailsToUser(restrictedAddresses[i]); + (lastTradedDayTime[i + restrictedAddresses.length], sumOfLastPeriod[i + restrictedAddresses.length], daysCovered[i + restrictedAddresses.length], dailyLastTradedDayTime[i + restrictedAddresses.length]) = + getDefaultBucketDetailsToUser(restrictedAddresses[i]); } - return (restrictedAddresses, typeOfPeriodRestriction); + /* return (allAddresses, typeOfPeriodRestriction, lastTradedDayTime, sumOfLastPeriod, daysCovered, dailyLastTradedDayTime); */ } /** @@ -1219,6 +1248,4 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { return allPermissions; } - - -} \ No newline at end of file +} diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index 004b58fa7..146c1c2b3 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -214,8 +214,8 @@ contract('VolumeRestrictionTransferManager', accounts => { await I_SecurityToken.mint(account_investor1, web3.utils.toWei("40", "ether"), {from: token_owner}); await I_SecurityToken.mint(account_investor2, web3.utils.toWei("30", "ether"), {from: token_owner}); await I_SecurityToken.mint(account_investor3, web3.utils.toWei("30", "ether"), {from: token_owner}); - - // Check the balance of the investors + + // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); let bal2 = await I_SecurityToken.balanceOf.call(account_investor2); // Verifying the balances @@ -406,10 +406,10 @@ contract('VolumeRestrictionTransferManager', accounts => { from: token_owner } ); - assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal(tx.logs[0].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + console.log(data); assert.equal(data[0][0], account_investor1); assert.equal(data[1][0].toNumber(), 0); @@ -544,6 +544,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor4))[2].toNumber(), 5); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + console.log(data); assert.equal(data[0].length, 4); }); @@ -563,7 +564,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); assert.equal(data[0].length, 3); - for (let i = 0; i < data.length; i++) { + for (let i = 0; i < data[0].length; i++) { assert.notEqual(data[0][i], account_investor2); assert.equal(data[1][i], 0); } @@ -615,7 +616,7 @@ contract('VolumeRestrictionTransferManager', accounts => { from: token_owner } ); - + assert.equal(tx.logs[1].args._holder, account_investor1); assert.equal(tx.logs[1].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); @@ -639,14 +640,14 @@ contract('VolumeRestrictionTransferManager', accounts => { Gas estimation (Individual): ${await I_SecurityToken.transfer.estimateGas(account_investor3, web3.utils.toWei('.3'), {from: account_investor1})}` ); await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('.3'), {from: account_investor1}); - // Check the balance of the investors + // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); // Verifying the balances assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 34.7); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor1); await print(data, account_investor1); - + assert.equal( (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor1, data[0])) .dividedBy(new BigNumber(10).pow(18)).toNumber(), @@ -760,7 +761,7 @@ contract('VolumeRestrictionTransferManager', accounts => { await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), {from: account_investor3}); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - + await increaseTime(duration.minutes(15)); console.log(` @@ -780,7 +781,7 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should fail to transfer more tokens --because of the above limit", async() => { await catchRevert( I_SecurityToken.transfer(account_investor2, web3.utils.toWei(".1"), {from: account_investor3}) - ); + ); }); it("Should try to send after the one day completion", async() => { @@ -841,7 +842,7 @@ contract('VolumeRestrictionTransferManager', accounts => { ); await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), {from: account_investor1}); - // Check the balance of the investors + // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); // Verifying the balances assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 32.7); @@ -882,7 +883,7 @@ contract('VolumeRestrictionTransferManager', accounts => { from: token_owner } ); - + assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 1); @@ -903,11 +904,11 @@ contract('VolumeRestrictionTransferManager', accounts => { console.log(` Gas estimation (Individual + Individual daily): ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("4"), {from: account_investor3})}` ); - // Check the balance of the investors + // Check the balance of the investors let bal1 = await I_SecurityToken.balanceOf.call(account_investor3); await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), {from: account_investor3}); tempArray3.push(4); - // Check the balance of the investors + // Check the balance of the investors let bal2 = await I_SecurityToken.balanceOf.call(account_investor3); // Verifying the balances assert.equal(web3.utils.fromWei(((bal1.minus(bal2)).toNumber()).toString()), 4); @@ -951,7 +952,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(dataAdd[1][0].toNumber(), 2); assert.equal(dataAdd[0][1], account_investor3); assert.equal(dataAdd[1][1].toNumber(), 0); - + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); // transfer more tokens on the same day @@ -1041,7 +1042,7 @@ contract('VolumeRestrictionTransferManager', accounts => { ) ); }); - + it("Should modify the individual daily restriction", async() => { await I_VolumeRestrictionTM.modifyIndividualDailyRestriction( account_investor3, @@ -1146,7 +1147,7 @@ contract('VolumeRestrictionTransferManager', accounts => { }); it("Should fail to transact tokens more than the allowed in the second rolling period", async() => { - await increaseTime(duration.days(4)); + await increaseTime(duration.days(4)); let i for (i = 0; i < 3; i++) { tempArray3.push(0); @@ -1167,7 +1168,7 @@ contract('VolumeRestrictionTransferManager', accounts => { let allowedAmount = (tempArray3[0] + 1); //sell tokens upto the limit await I_SecurityToken.transfer(account_investor2, web3.utils.toWei(allowedAmount.toString()), {from: account_investor3}); - + tempArray3.push(allowedAmount); let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); @@ -1196,11 +1197,11 @@ contract('VolumeRestrictionTransferManager', accounts => { //sell tokens upto the limit await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("7"), {from: account_investor3}); - + tempArray3.push(7) let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - + // get the trade amount using the timestamp let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) .dividedBy(new BigNumber(10).pow(18)).toNumber(); @@ -1231,7 +1232,7 @@ contract('VolumeRestrictionTransferManager', accounts => { let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - + // get the trade amount using the timestamp let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) .dividedBy(new BigNumber(10).pow(18)).toNumber(); @@ -1252,7 +1253,7 @@ contract('VolumeRestrictionTransferManager', accounts => { }); describe("Test cases for the Default restrictions", async() => { - + it("Should add the investor 4 in the whitelist", async() => { await I_GeneralTransferManager.modifyWhitelist( account_investor4, @@ -1302,10 +1303,10 @@ contract('VolumeRestrictionTransferManager', accounts => { let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); //sell tokens upto the limit await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3.57"), {from: account_investor4}); - + let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor4); await print(data, account_investor3); - + // get the trade amount using the timestamp let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor4, data[3].toNumber())) .dividedBy(new BigNumber(10).pow(18)).toNumber(); @@ -1365,7 +1366,7 @@ contract('VolumeRestrictionTransferManager', accounts => { let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - + // get the trade amount using the timestamp let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) .dividedBy(new BigNumber(10).pow(18)).toNumber(); @@ -1421,7 +1422,7 @@ contract('VolumeRestrictionTransferManager', accounts => { let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - + // get the trade amount using the timestamp let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) .dividedBy(new BigNumber(10).pow(18)).toNumber(); @@ -1478,7 +1479,7 @@ contract('VolumeRestrictionTransferManager', accounts => { let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); await print(data, account_investor3); - + // get the trade amount using the timestamp let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) .dividedBy(new BigNumber(10).pow(18)).toNumber(); @@ -1593,5 +1594,5 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Maximum Volume"); }); }); - -}); \ No newline at end of file + +}); From ea5089a565c8f9cb54a81edd33cb000beedceab6 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Tue, 8 Jan 2019 16:17:14 -0400 Subject: [PATCH 04/17] Remove commented code and better tests --- .../TransferManager/VolumeRestrictionTM.sol | 36 ++----------------- test/y_volume_restriction_tm.js | 21 +++++++++-- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index eb9475360..e19f937ad 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -183,7 +183,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { internal { - /* uint256 startTime = _startTime; */ if (_startTime == 0) { _startTime = now; } @@ -192,7 +191,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { "Not Allowed" ); require(_holder != address(0) && !exemptList[_holder], "Invalid address"); - /* require(_startTime >= now, "Invalid startTime"); */ _checkInputParams(_allowedTokens, _startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); if (individualRestriction[_holder].endTime != 0) { @@ -263,8 +261,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ) internal { - - /* uint256 startTime = _startTime; */ if (_startTime == 0) { _startTime = now; } @@ -272,7 +268,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { individualDailyRestriction[_holder].endTime < now, "Not Allowed" ); - /* require(startTime >= now, "Invalid startTime"); */ _checkInputParams(_allowedTokens, _startTime, 1, _endTime, _restrictionType, now); individualDailyRestriction[_holder] = VolumeRestriction( _allowedTokens, @@ -310,14 +305,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { public withPerm(ADMIN) { + //NB - we duplicate _startTimes below to allow function reuse _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); - /* require( - _allowedTokens.length == _startTimes.length && - _startTimes.length == _holders.length && - _holders.length == _endTimes.length && - _endTimes.length == _restrictionTypes.length, - "Array length mismatch" - ); */ for (uint256 i = 0; i < _holders.length; i++) { _addIndividualDailyRestriction( _holders[i], @@ -350,7 +339,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { withPerm(ADMIN) { _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); - /* require(_holders.length == _allowedTokens.length, "Length mismatch"); */ for (uint256 i = 0; i < _holders.length; i++) { _addIndividualRestriction( _holders[i], @@ -389,7 +377,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { defaultRestriction.endTime < now, "Not Allowed" ); - /* require(startTime >= now, "Invalid startTime"); */ _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); defaultRestriction = VolumeRestriction( _allowedTokens, @@ -431,7 +418,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { defaultDailyRestriction.endTime < now, "Not Allowed" ); - /* require(startTime >= now, "Invalid startTime"); */ _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, now); defaultDailyRestriction = VolumeRestriction( _allowedTokens, @@ -592,7 +578,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _startTime = now; } require(individualRestriction[_holder].startTime > now, "Not Allowed"); - /* require(startTime >= now, "Invalid startTime"); */ _checkInputParams(_allowedTokens, _startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); individualRestriction[_holder] = VolumeRestriction( _allowedTokens, @@ -650,19 +635,12 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ) internal { - /* uint256 startTime = _startTime; */ if (_startTime == 0) { _startTime = now; } _checkInputParams(_allowedTokens, _startTime, 1, _endTime, _restrictionType, (individualDailyRestriction[_holder].startTime <= now ? individualDailyRestriction[_holder].startTime : now) ); - // If old startTime is already passed then new startTime should be greater than or equal to the - // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. - /* if (individualDailyRestriction[_holder].startTime <= now) - require(startTime >= individualDailyRestriction[_holder].startTime, "Invalid StartTime"); - else - require(startTime >= now); */ individualDailyRestriction[_holder] = VolumeRestriction( _allowedTokens, _startTime, @@ -698,14 +676,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { public withPerm(ADMIN) { - require( - _allowedTokens.length == _startTimes.length && - _startTimes.length == _holders.length && - _holders.length == _endTimes.length && - _endTimes.length == _restrictionTypes.length, - "Length mismatch" - ); - require(_holders.length == _allowedTokens.length, "Length mismatch"); + //NB - we duplicate _startTimes below to allow function reuse + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { _modifyIndividualDailyRestriction( _holders[i], @@ -738,7 +710,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { withPerm(ADMIN) { _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); - /* require(_holders.length == _allowedTokens.length, "Length mismatch"); */ for (uint256 i = 0; i < _holders.length; i++) { _modifyIndividualRestriction( _holders[i], @@ -1236,7 +1207,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { (lastTradedDayTime[i + restrictedAddresses.length], sumOfLastPeriod[i + restrictedAddresses.length], daysCovered[i + restrictedAddresses.length], dailyLastTradedDayTime[i + restrictedAddresses.length]) = getDefaultBucketDetailsToUser(restrictedAddresses[i]); } - /* return (allAddresses, typeOfPeriodRestriction, lastTradedDayTime, sumOfLastPeriod, daysCovered, dailyLastTradedDayTime); */ } /** diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index 146c1c2b3..c57ce68f5 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -89,6 +89,16 @@ contract('VolumeRestrictionTransferManager', accounts => { `) } + async function printAll(data) { + let investors = data[0]; + for (let i = 0; i < investors.length; i++) { + console.log("Individual Restrictions: "); + await print([data[2][i], data[3][i], data[4][i], data[5][i]], investors[i]); + console.log("Daily Restrictions: "); + await print([data[2][i + investors.length], data[3][i + investors.length], data[4][i + investors.length], data[5][i + investors.length]], investors[i]); + } + } + async function calculateSum(rollingPeriod, tempArray) { let sum = 0; let start = 0; @@ -409,7 +419,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal(tx.logs[0].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - console.log(data); + await printAll(data); assert.equal(data[0][0], account_investor1); assert.equal(data[1][0].toNumber(), 0); @@ -544,7 +554,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor4))[2].toNumber(), 5); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - console.log(data); + await printAll(data); assert.equal(data[0].length, 4); }); @@ -563,6 +573,7 @@ contract('VolumeRestrictionTransferManager', accounts => { await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(data); assert.equal(data[0].length, 3); for (let i = 0; i < data[0].length; i++) { assert.notEqual(data[0][i], account_investor2); @@ -584,6 +595,7 @@ contract('VolumeRestrictionTransferManager', accounts => { } ) let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(data); assert.equal(data[0].length, 1); }); @@ -620,6 +632,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[1].args._holder, account_investor1); assert.equal(tx.logs[1].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(data); assert.equal(data[0].length, 1); assert.equal(data[0][0], account_investor1); }); @@ -737,6 +750,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._typeOfRestriction, 0); assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("6")); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); assert.equal(data[1][1], 1); @@ -817,6 +831,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((tx.logs[0].args._typeOfRestriction).toNumber(), 1); assert.equal((tx.logs[0].args._allowedTokens).dividedBy(new BigNumber(10).pow(16)).toNumber(), 5); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); assert.equal(data[0][0], account_investor1); @@ -888,6 +903,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._typeOfRestriction, 1); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); assert.equal(data[0][0], account_investor1); @@ -947,6 +963,7 @@ contract('VolumeRestrictionTransferManager', accounts => { let tx = await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: token_owner}); assert.equal(tx.logs[0].args._holder, account_investor3); let dataAdd = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + await printAll(dataAdd); assert.equal(dataAdd[0].length, 2); assert.equal(dataAdd[0][0], account_investor1); assert.equal(dataAdd[1][0].toNumber(), 2); From 7429d130eff21f81192c94936ae68a5f3b1aa5c3 Mon Sep 17 00:00:00 2001 From: satyam Date: Wed, 9 Jan 2019 14:58:32 +0530 Subject: [PATCH 05/17] modified the getRestrictedAddress() function --- .../TransferManager/VolumeRestrictionTM.sol | 66 ++++++++++++++-- test/y_volume_restriction_tm.js | 79 +++++++++++-------- 2 files changed, 109 insertions(+), 36 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index e19f937ad..3a212df4f 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -136,7 +136,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @param _change Boolean value used to add (i.e true) or remove (i.e false) from the list */ function changeExemptWalletList(address _wallet, bool _change) public withPerm(ADMIN) { - require(_wallet != address(0), "Invalid address"); + require(_wallet != address(0)); exemptList[_wallet] = _change; emit ChangedExemptWalletList(_wallet, _change); } @@ -446,7 +446,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /// @notice Internal function to facilitate the removal of individual restriction function _removeIndividualRestriction(address _holder) internal { require(_holder != address(0), "Invalid address"); - require(individualRestriction[_holder].endTime != 0, "Not present"); + require(individualRestriction[_holder].endTime != 0); individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); _deleteHolderFromList(_holder, uint8(TypeOfPeriod.OneDay)); userToBucket[_holder].lastTradedDayTime = 0; @@ -497,7 +497,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /// @notice Internal function to facilitate the removal of individual daily restriction function _removeIndividualDailyRestriction(address _holder) internal { require(_holder != address(0), "Invalid address"); - require(individualDailyRestriction[_holder].endTime != 0, "Not present"); + require(individualDailyRestriction[_holder].endTime != 0); individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); _deleteHolderFromList(_holder, uint8(TypeOfPeriod.MultipleDays)); userToBucket[_holder].dailyLastTradedDayTime = 0; @@ -1088,7 +1088,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { } else { require( _allowedTokens > 0 && _allowedTokens <= 100 * 10 ** 16, - "Percentage is not within (0,100]" + "Invalid value" ); } require(_endTime > _startTime, "Invalid times"); @@ -1186,7 +1186,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256[] List of daysCovered, first group for individuals, second group for default * @return uint256[] List of dailyLastTradedDayTime, first group for individuals, second group for default */ - function getRestrictedAddresses() external view returns( + /*function getRestrictedAddresses() external view returns( address[] memory allAddresses, uint8[] memory typeOfPeriodRestriction, uint256[] memory lastTradedDayTime, @@ -1207,6 +1207,62 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { (lastTradedDayTime[i + restrictedAddresses.length], sumOfLastPeriod[i + restrictedAddresses.length], daysCovered[i + restrictedAddresses.length], dailyLastTradedDayTime[i + restrictedAddresses.length]) = getDefaultBucketDetailsToUser(restrictedAddresses[i]); } + }*/ + + function getRestrictedAddresses() external view returns( + address[] memory allAddresses, + uint256[] memory allowedTokens, + uint256[] memory startTime, + uint256[] memory rollingPeriodInDays, + uint256[] memory endTime, + uint8[] memory typeOfRestriction + ) { + uint256 counter = 0; + uint256 i = 0; + for (i = 0; i < restrictedAddresses.length; i++) { + counter = counter + (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(2) ? 2 : 1); + } + allAddresses = new address[](counter); + allowedTokens = new uint256[](counter); + startTime = new uint256[](counter); + rollingPeriodInDays = new uint256[](counter); + endTime = new uint256[](counter); + typeOfRestriction = new uint8[](counter); + counter = 0; + for (i = 0; i < restrictedAddresses.length; i++) { + allAddresses[counter] = restrictedAddresses[i]; + if (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.MultipleDays)) { + _setValues(individualRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + else if (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.OneDay)) { + _setValues(individualDailyRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + else if (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.Both)) { + _setValues(individualRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + counter = counter + 1; + allAddresses[counter] = restrictedAddresses[i]; + _setValues(individualDailyRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + counter ++; + } + } + + function _setValues( + VolumeRestriction memory restriction, + uint256[] memory allowedTokens, + uint256[] memory startTime, + uint256[] memory rollingPeriodInDays, + uint256[] memory endTime, + uint8[] memory typeOfRestriction, + uint256 index + ) + internal + { + allowedTokens[index] = restriction.allowedTokens; + startTime[index] = restriction.startTime; + rollingPeriodInDays[index] = restriction.rollingPeriodInDays; + endTime[index] = restriction.endTime; + typeOfRestriction[index] = uint8(restriction.typeOfRestriction); } /** diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index c57ce68f5..90345b6ae 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -89,13 +89,28 @@ contract('VolumeRestrictionTransferManager', accounts => { `) } - async function printAll(data) { + // async function printAll(data) { + // let investors = data[0]; + // for (let i = 0; i < investors.length; i++) { + // console.log("Individual Restrictions: "); + // await print([data[2][i], data[3][i], data[4][i], data[5][i]], investors[i]); + // console.log("Daily Restrictions: "); + // await print([data[2][i + investors.length], data[3][i + investors.length], data[4][i + investors.length], data[5][i + investors.length]], investors[i]); + // } + // } + + async function printRestrictedData(data) { let investors = data[0]; - for (let i = 0; i < investors.length; i++) { - console.log("Individual Restrictions: "); - await print([data[2][i], data[3][i], data[4][i], data[5][i]], investors[i]); - console.log("Daily Restrictions: "); - await print([data[2][i + investors.length], data[3][i + investors.length], data[4][i + investors.length], data[5][i + investors.length]], investors[i]); + for (let i = 0 ; i < investors.length; i++) { + console.log(` + Token holder: ${data[0][i]} + Start Time: ${data[2][i].toNumber()} + Rolling Period In Days: ${data[3][i].toNumber()} + End Time : ${data[4][i].toNumber()} + Allowed Tokens: ${web3.utils.fromWei(data[1][i].toString())} + Type of Restriction: ${data[5][i].toNumber()} + `) + console.log("\n"); } } @@ -419,10 +434,8 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal(tx.logs[0].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); + await printRestrictedData(data); assert.equal(data[0][0], account_investor1); - assert.equal(data[1][0].toNumber(), 0); - }); it("Should add the restriction for multiple investor -- failed because of bad owner", async() => { @@ -554,7 +567,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor4))[2].toNumber(), 5); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); + await printRestrictedData(data); assert.equal(data[0].length, 4); }); @@ -573,11 +586,11 @@ contract('VolumeRestrictionTransferManager', accounts => { await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); + await printRestrictedData(data); + //await printAll(data); assert.equal(data[0].length, 3); for (let i = 0; i < data[0].length; i++) { assert.notEqual(data[0][i], account_investor2); - assert.equal(data[1][i], 0); } }); @@ -595,7 +608,8 @@ contract('VolumeRestrictionTransferManager', accounts => { } ) let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); + await printRestrictedData(data); + //await printAll(data); assert.equal(data[0].length, 1); }); @@ -632,7 +646,8 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[1].args._holder, account_investor1); assert.equal(tx.logs[1].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); + await printRestrictedData(data); + //await printAll(data); assert.equal(data[0].length, 1); assert.equal(data[0][0], account_investor1); }); @@ -750,10 +765,11 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._typeOfRestriction, 0); assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("6")); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); + await printRestrictedData(data); + //await printAll(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); - assert.equal(data[1][1], 1); + //assert.equal(data[1][1], 1); let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); console.log(` *** Individual Daily restriction data *** @@ -831,12 +847,13 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((tx.logs[0].args._typeOfRestriction).toNumber(), 1); assert.equal((tx.logs[0].args._allowedTokens).dividedBy(new BigNumber(10).pow(16)).toNumber(), 5); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); - assert.equal(data[0].length, 2); - assert.equal(data[0][1], account_investor3); + await printRestrictedData(data); + //await printAll(data); + assert.equal(data[0].length, 3); + assert.equal(data[0][2], account_investor3); assert.equal(data[0][0], account_investor1); - assert.equal(data[1][1].toNumber(), 1); - assert.equal(data[1][0].toNumber(), 2); + // // assert.equal(data[1][1].toNumber(), 1); + // // assert.equal(data[1][0].toNumber(), 2); let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1); console.log(` *** Individual Daily restriction data *** @@ -903,12 +920,13 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._typeOfRestriction, 1); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(data); - assert.equal(data[0].length, 2); - assert.equal(data[0][1], account_investor3); + await printRestrictedData(data); + //await printAll(data); + assert.equal(data[0].length, 4); + assert.equal(data[0][2], account_investor3); assert.equal(data[0][0], account_investor1); - assert.equal(data[1][1].toNumber(), 2); - assert.equal(data[1][0].toNumber(), 2); + // assert.equal(data[1][1].toNumber(), 2); + // assert.equal(data[1][0].toNumber(), 2); }); it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async() => { @@ -963,12 +981,11 @@ contract('VolumeRestrictionTransferManager', accounts => { let tx = await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: token_owner}); assert.equal(tx.logs[0].args._holder, account_investor3); let dataAdd = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); - await printAll(dataAdd); - assert.equal(dataAdd[0].length, 2); + await printRestrictedData(dataAdd); + // //await printAll(dataAdd); + assert.equal(dataAdd[0].length, 3); assert.equal(dataAdd[0][0], account_investor1); - assert.equal(dataAdd[1][0].toNumber(), 2); - assert.equal(dataAdd[0][1], account_investor3); - assert.equal(dataAdd[1][1].toNumber(), 0); + assert.equal(dataAdd[0][2], account_investor3); let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); From 72c1eb7b46dc1772dfed37a79f0020c3add667d0 Mon Sep 17 00:00:00 2001 From: satyam Date: Wed, 9 Jan 2019 18:11:26 +0530 Subject: [PATCH 06/17] code cleanup --- .../TransferManager/VolumeRestrictionTM.sol | 50 ++++--------------- test/y_volume_restriction_tm.js | 23 --------- 2 files changed, 11 insertions(+), 62 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index 3a212df4f..93b8bc6df 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -785,15 +785,11 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { if (_startTime == 0) { startTime = now; } + // If old startTime is already passed then new startTime should be greater than or equal to the + // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, (defaultDailyRestriction.startTime <= now ? defaultDailyRestriction.startTime : now) ); - // If old startTime is already passed then new startTime should be greater than or equal to the - // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. - /* if (defaultDailyRestriction.startTime <= now) - require(startTime >= defaultDailyRestriction.startTime, "Invalid StartTime"); - else - require(startTime >= now); */ defaultDailyRestriction = VolumeRestriction( _allowedTokens, startTime, @@ -1174,41 +1170,17 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { else return _callFrom; } - + /** - * @notice use to get the list of token holders who are restricted by the VRTM - * @return address[] List of addresses that are restricted by the VRTM - * @return uint8[] Array of the Type of period restriction on the addresses. 0 - address - * has only individual restriction, 1 - address has only individual daily restriction & 2 - * it means address has both type of restriction where rolling period is 24 hrs & multiple days as well - * @return uint256[] List of lastTradedDayTime, first group for individuals, second group for default - * @return uint256[] List of sumOfLastPeriod, first group for individuals, second group for default - * @return uint256[] List of daysCovered, first group for individuals, second group for default - * @return uint256[] List of dailyLastTradedDayTime, first group for individuals, second group for default + * @notice Provide the restriction details of all the restricted addresses + * @return address List of the restricted addresses + * @return uint256 List of the tokens allowed to the restricted addresses corresponds to restricted address + * @return uint256 List of the start time of the restriction corresponds to restricted address + * @return uint256 List of the rolling period in days for a restriction corresponds to restricted address. + * @return uint256 List of the end time of the restriction corresponds to restricted address. + * @return uint8 List of the type of restriction to validate the value of the `allowedTokens` + * of the restriction corresponds to restricted address */ - /*function getRestrictedAddresses() external view returns( - address[] memory allAddresses, - uint8[] memory typeOfPeriodRestriction, - uint256[] memory lastTradedDayTime, - uint256[] memory sumOfLastPeriod, - uint256[] memory daysCovered, - uint256[] memory dailyLastTradedDayTime - ) { - allAddresses = restrictedAddresses; - typeOfPeriodRestriction = new uint8[](restrictedAddresses.length); - lastTradedDayTime = new uint256[](2 * restrictedAddresses.length); - sumOfLastPeriod = new uint256[](2 * restrictedAddresses.length); - daysCovered = new uint256[](2 * restrictedAddresses.length); - dailyLastTradedDayTime = new uint256[](2 * restrictedAddresses.length); - for (uint256 i = 0; i < restrictedAddresses.length; i++) { - typeOfPeriodRestriction[i] = restrictedHolders[restrictedAddresses[i]].typeOfPeriod; - (lastTradedDayTime[i], sumOfLastPeriod[i], daysCovered[i], dailyLastTradedDayTime[i]) = - getIndividualBucketDetailsToUser(restrictedAddresses[i]); - (lastTradedDayTime[i + restrictedAddresses.length], sumOfLastPeriod[i + restrictedAddresses.length], daysCovered[i + restrictedAddresses.length], dailyLastTradedDayTime[i + restrictedAddresses.length]) = - getDefaultBucketDetailsToUser(restrictedAddresses[i]); - } - }*/ - function getRestrictedAddresses() external view returns( address[] memory allAddresses, uint256[] memory allowedTokens, diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index 90345b6ae..93606f745 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -89,16 +89,6 @@ contract('VolumeRestrictionTransferManager', accounts => { `) } - // async function printAll(data) { - // let investors = data[0]; - // for (let i = 0; i < investors.length; i++) { - // console.log("Individual Restrictions: "); - // await print([data[2][i], data[3][i], data[4][i], data[5][i]], investors[i]); - // console.log("Daily Restrictions: "); - // await print([data[2][i + investors.length], data[3][i + investors.length], data[4][i + investors.length], data[5][i + investors.length]], investors[i]); - // } - // } - async function printRestrictedData(data) { let investors = data[0]; for (let i = 0 ; i < investors.length; i++) { @@ -110,7 +100,6 @@ contract('VolumeRestrictionTransferManager', accounts => { Allowed Tokens: ${web3.utils.fromWei(data[1][i].toString())} Type of Restriction: ${data[5][i].toNumber()} `) - console.log("\n"); } } @@ -587,7 +576,6 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(data); - //await printAll(data); assert.equal(data[0].length, 3); for (let i = 0; i < data[0].length; i++) { assert.notEqual(data[0][i], account_investor2); @@ -609,7 +597,6 @@ contract('VolumeRestrictionTransferManager', accounts => { ) let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(data); - //await printAll(data); assert.equal(data[0].length, 1); }); @@ -647,7 +634,6 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[1].args._typeOfRestriction, 0); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(data); - //await printAll(data); assert.equal(data[0].length, 1); assert.equal(data[0][0], account_investor1); }); @@ -766,10 +752,8 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("6")); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(data); - //await printAll(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); - //assert.equal(data[1][1], 1); let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); console.log(` *** Individual Daily restriction data *** @@ -848,12 +832,9 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((tx.logs[0].args._allowedTokens).dividedBy(new BigNumber(10).pow(16)).toNumber(), 5); let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(data); - //await printAll(data); assert.equal(data[0].length, 3); assert.equal(data[0][2], account_investor3); assert.equal(data[0][0], account_investor1); - // // assert.equal(data[1][1].toNumber(), 1); - // // assert.equal(data[1][0].toNumber(), 2); let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1); console.log(` *** Individual Daily restriction data *** @@ -921,12 +902,9 @@ contract('VolumeRestrictionTransferManager', accounts => { let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(data); - //await printAll(data); assert.equal(data[0].length, 4); assert.equal(data[0][2], account_investor3); assert.equal(data[0][0], account_investor1); - // assert.equal(data[1][1].toNumber(), 2); - // assert.equal(data[1][0].toNumber(), 2); }); it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async() => { @@ -982,7 +960,6 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); let dataAdd = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); await printRestrictedData(dataAdd); - // //await printAll(dataAdd); assert.equal(dataAdd[0].length, 3); assert.equal(dataAdd[0][0], account_investor1); assert.equal(dataAdd[0][2], account_investor3); From f400049fe258ec8a66ed31af0724298619c0e4d6 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 09:49:30 -0300 Subject: [PATCH 07/17] CLI - Show restrictions table --- CLI/commands/common/common_functions.js | 4 +-- CLI/commands/transfer_manager.js | 44 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CLI/commands/common/common_functions.js b/CLI/commands/common/common_functions.js index 7a9195891..40665d514 100644 --- a/CLI/commands/common/common_functions.js +++ b/CLI/commands/common/common_functions.js @@ -158,8 +158,8 @@ module.exports = { }, splitIntoBatches: function (data, batchSize) { let allBatches = []; - for (let index = 0; index < data.length; index += batchSize) { - allBatches.push(data.slice(index, index + batchSize)); + for (let index = 0; index < data.length; index += parseInt(batchSize)) { + allBatches.push(data.slice(index, index + parseInt(batchSize))); } return allBatches; }, diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 2b76a8f80..aea833d6c 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -1472,6 +1472,23 @@ async function addInvestorsToBlacklistsInBatch() { } } +function makeBatchRequest(calls) { + let batch = new web3.BatchRequest(); + + let promises = calls.map(call => { + return new Promise((res, rej) => { + let req = call.request({ from: Issuer.address }, (err, data) => { + if (err) rej(err); + else res(data) + }); + batch.add(req) + }) + }) + batch.execute() + + return Promise.all(promises) +} + async function removeInvestorsFromBlacklistsInBatch() { let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_INVESTOR_BLACKLIST_DATA_CSV}): `, { defaultInput: REMOVE_INVESTOR_BLACKLIST_DATA_CSV @@ -1529,6 +1546,7 @@ async function volumeRestrictionTM() { } let options = [ + 'Show restrictios', 'Change exempt wallet', 'Change default restrictions', 'Change individual restrictions', @@ -1540,6 +1558,17 @@ async function volumeRestrictionTM() { let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { + case 'Show restrictios': + let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedAddresses().call(); + showRestrictionTable( + addressesAndRestrictions[0], + addressesAndRestrictions[1], + addressesAndRestrictions[5], + addressesAndRestrictions[3], + addressesAndRestrictions[2], + addressesAndRestrictions[4], + ); + break; case 'Change exempt wallet': await changeExemptWallet(); break; @@ -1562,6 +1591,21 @@ async function volumeRestrictionTM() { await volumeRestrictionTM(); } +function showRestrictionTable(investorArray, amountArray, typeArray, rollingPeriodArray, startTimeArray, endTimeTimeArray) { + let dataTable = [['Investor', 'Maximum transfer (# or %)', 'Rolling period (days)', 'Start date', 'End date']]; + for (let i = 0; i < investorArray.length; i++) { + dataTable.push([ + investorArray[i], + typeArray[i] === "0" ? `${web3.utils.fromWei(amountArray[i])} ${tokenSymbol}` : `${fromWeiPercentage(amountArray[i])}%`, + rollingPeriodArray[i], + moment.unix(startTimeArray[i]).format('MM/DD/YYYY HH:mm'), + moment.unix(endTimeTimeArray[i]).format('MM/DD/YYYY HH:mm') + ]); + } + console.log(); + console.log(table(dataTable)); +} + async function changeExemptWallet() { let options = [ 'Add exempt wallet', From 3638c60dad85c104103ce6d1bbecbc4b59b43b53 Mon Sep 17 00:00:00 2001 From: satyam Date: Wed, 9 Jan 2019 18:40:55 +0530 Subject: [PATCH 08/17] minor fix --- .../TransferManager/VolumeRestrictionTM.sol | 3 ++- test/y_volume_restriction_tm.js | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index 93b8bc6df..2bfa4edc2 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -1181,7 +1181,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint8 List of the type of restriction to validate the value of the `allowedTokens` * of the restriction corresponds to restricted address */ - function getRestrictedAddresses() external view returns( + function getRestrictedData() external view returns( address[] memory allAddresses, uint256[] memory allowedTokens, uint256[] memory startTime, @@ -1229,6 +1229,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256 index ) internal + pure { allowedTokens[index] = restriction.allowedTokens; startTime[index] = restriction.startTime; diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index 93606f745..3610c13a3 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -422,7 +422,7 @@ contract('VolumeRestrictionTransferManager', accounts => { ); assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal(tx.logs[0].args._typeOfRestriction, 0); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0][0], account_investor1); }); @@ -555,7 +555,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3))[2].toNumber(), 4); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor4))[2].toNumber(), 5); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 4); }); @@ -574,7 +574,7 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should successfully remove the restriction", async() => { await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}); assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_investor2))[3].toNumber(), 0); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 3); for (let i = 0; i < data[0].length; i++) { @@ -595,7 +595,7 @@ contract('VolumeRestrictionTransferManager', accounts => { from: token_owner } ) - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 1); }); @@ -632,7 +632,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[1].args._holder, account_investor1); assert.equal(tx.logs[1].args._typeOfRestriction, 0); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 1); assert.equal(data[0][0], account_investor1); @@ -750,7 +750,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 0); assert.equal((tx.logs[0].args._allowedTokens).toNumber(), web3.utils.toWei("6")); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 2); assert.equal(data[0][1], account_investor3); @@ -830,7 +830,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor1); assert.equal((tx.logs[0].args._typeOfRestriction).toNumber(), 1); assert.equal((tx.logs[0].args._allowedTokens).dividedBy(new BigNumber(10).pow(16)).toNumber(), 5); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 3); assert.equal(data[0][2], account_investor3); @@ -900,7 +900,7 @@ contract('VolumeRestrictionTransferManager', accounts => { assert.equal(tx.logs[0].args._holder, account_investor3); assert.equal(tx.logs[0].args._typeOfRestriction, 1); - let data = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(data); assert.equal(data[0].length, 4); assert.equal(data[0][2], account_investor3); @@ -958,7 +958,7 @@ contract('VolumeRestrictionTransferManager', accounts => { // remove the Individual daily restriction let tx = await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: token_owner}); assert.equal(tx.logs[0].args._holder, account_investor3); - let dataAdd = await I_VolumeRestrictionTM.getRestrictedAddresses.call(); + let dataAdd = await I_VolumeRestrictionTM.getRestrictedData.call(); await printRestrictedData(dataAdd); assert.equal(dataAdd[0].length, 3); assert.equal(dataAdd[0][0], account_investor1); From bb4c1feeaadf0a0f5bf45b5a71ba54448ad15c2c Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 10:20:43 -0300 Subject: [PATCH 09/17] CLI - Update method name, updated csv dates --- CLI/commands/transfer_manager.js | 31 +++++++++++-------- .../VRTM/add_daily_restriction_data.csv | 16 +++++----- .../Transfer/VRTM/add_restriction_data.csv | 16 +++++----- .../VRTM/modify_daily_restriction_data.csv | 10 +++--- .../Transfer/VRTM/modify_restriction_data.csv | 10 +++--- 5 files changed, 44 insertions(+), 39 deletions(-) diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index aea833d6c..929d43017 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -1536,7 +1536,7 @@ async function volumeRestrictionTM() { console.log(` Rolling period: ${defaultDailyRestriction.rollingPeriodInDays} days`); console.log(` End time: ${moment.unix(defaultDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Default restriction: ${hasDefaultRestriction ? '' : 'None'} `); + console.log(`- Default restriction: ${hasDefaultRestriction ? '' : 'None'}`); if (hasDefaultRestriction) { console.log(` Type: ${RESTRICTION_TYPES[defaultRestriction.typeOfRestriction]}`); console.log(` Allowed tokens: ${defaultRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(defaultRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(defaultRestriction.allowedTokens)}%`}`); @@ -1545,28 +1545,33 @@ async function volumeRestrictionTM() { console.log(` End time: ${moment.unix(defaultRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - let options = [ - 'Show restrictios', + let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedData().call(); + console.log(`- Individual restrictions: ${addressesAndRestrictions.allAddresses.length}`); + + let options = []; + if (addressesAndRestrictions[0].length > 0) { + options.push('Show restrictios'); + } + options.push( 'Change exempt wallet', 'Change default restrictions', 'Change individual restrictions', 'Explore account', 'Operate with multiple restrictions' - ]; + ); 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 'Show restrictios': - let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedAddresses().call(); showRestrictionTable( - addressesAndRestrictions[0], - addressesAndRestrictions[1], - addressesAndRestrictions[5], - addressesAndRestrictions[3], - addressesAndRestrictions[2], - addressesAndRestrictions[4], + addressesAndRestrictions.allAddresses, + addressesAndRestrictions.allowedTokens, + addressesAndRestrictions.typeOfRestriction, + addressesAndRestrictions.rollingPeriodInDays, + addressesAndRestrictions.startTime, + addressesAndRestrictions.endTime, ); break; case 'Change exempt wallet': @@ -1745,7 +1750,7 @@ async function changeIndividualRestrictions() { console.log(` Rolling period: ${currentDailyRestriction.rollingPeriodInDays} days`); console.log(` End time: ${moment.unix(currentDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Other restriction: ${hasRestriction ? '' : 'None'} `); + console.log(`- Other restriction: ${hasRestriction ? '' : 'None'} `); if (hasRestriction) { console.log(` Type: ${RESTRICTION_TYPES[currentRestriction.typeOfRestriction]}`); console.log(` Allowed tokens: ${currentRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentRestriction.allowedTokens)}%`}`); @@ -2164,7 +2169,7 @@ function inputRestrictionData(isDaily) { } restriction.startTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) at which restriction get into effect (now = 0): `, { defaultInput: 0 }); let oneMonthFromNow = Math.floor(Date.now() / 1000) + gbl.constants.DURATION.days(30); - restriction.endTime = readlineSync.question(`Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others (1 week from now = ${oneMonthFromNow}): `, { + restriction.endTime = readlineSync.question(`Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others (1 month from now = ${oneMonthFromNow}): `, { limit: function (input) { return input > restriction.startTime + gbl.constants.DURATION.days(restriction.rollingPeriodInDays); }, diff --git a/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv b/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv index 782c1107d..1486579b7 100644 --- a/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv +++ b/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv @@ -1,8 +1,8 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,1/8/2019,10/10/2019,"Fixed" -0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,1/8/2019,10/10/2019,"Fixed" -0xac297053173b02b02a737d47f7b4a718e5b170ef,500,1/8/2019,10/10/2019,"Fixed" -0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,1/8/2019,10/10/2019,"Percentage" -0x10223927009b8add0960359dd90d1449415b7ca9,0.25,1/8/2019,10/10/2019,"Percentage" -0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,1/8/2019,10/10/2019,"Percentage" -0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,1/8/2019,10/10/2019,"Fixed" -0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,1/8/2019,10/10/2019,"Fixed" \ No newline at end of file +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,8/1/2019,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,10/12/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,10/10/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/3/2019,10/1/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,0.25,8/1/2019,10/10/2019,"Percentage" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,8/1/2019,10/5/2019,"Percentage" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,8/12/2019,10/10/2019,"Fixed" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,8/1/2019,10/10/2019,"Fixed" \ No newline at end of file diff --git a/CLI/data/Transfer/VRTM/add_restriction_data.csv b/CLI/data/Transfer/VRTM/add_restriction_data.csv index 072151a64..1807e90e6 100644 --- a/CLI/data/Transfer/VRTM/add_restriction_data.csv +++ b/CLI/data/Transfer/VRTM/add_restriction_data.csv @@ -1,8 +1,8 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,1/8/2019,90,10/10/2019,"Fixed" -0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,1/8/2019,30,10/10/2019,"Fixed" -0xac297053173b02b02a737d47f7b4a718e5b170ef,500,1/8/2019,15,10/10/2019,"Fixed" -0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,1/8/2019,90,10/10/2019,"Percentage" -0x10223927009b8add0960359dd90d1449415b7ca9,0.25,1/8/2019,30,10/10/2019,"Percentage" -0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,1/8/2019,15,10/10/2019,"Percentage" -0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,1/8/2019,10,10/10/2019,"Fixed" -0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,1/8/2019,2,10/10/2019,"Fixed" \ No newline at end of file +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,8/1/2019,90,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,30,10/12/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,15,10/1/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/5/2019,90,10/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,0.25,8/3/2019,30,10/15/2019,"Percentage" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,8/10/2019,15,10/10/2019,"Percentage" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,8/20/2019,10,10/22/2019,"Fixed" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,8/1/2019,2,10/10/2019,"Fixed" \ No newline at end of file diff --git a/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv index 194e9b1d5..908c164e9 100644 --- a/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv +++ b/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv @@ -1,5 +1,5 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,1/8/2019,10/10/2019,"Fixed" -0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,2/8/2019,10/10/2019,"Fixed" -0xac297053173b02b02a737d47f7b4a718e5b170ef,500,1/8/2019,10/10/2019,"Fixed" -0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,1/8/2019,20/10/2019,"Percentage" -0x10223927009b8add0960359dd90d1449415b7ca9,25,1/8/2019,10/10/2019,"Fixed" +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,8/1/2019,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/1/2019,10/10/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,10/10/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/1/2019,20/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,25,8/1/2019,10/10/2019,"Fixed" diff --git a/CLI/data/Transfer/VRTM/modify_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_restriction_data.csv index 588e39211..b69f43361 100644 --- a/CLI/data/Transfer/VRTM/modify_restriction_data.csv +++ b/CLI/data/Transfer/VRTM/modify_restriction_data.csv @@ -1,5 +1,5 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,1/8/2019,90,10/10/2019,"Fixed" -0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,2/8/2019,30,10/10/2019,"Fixed" -0xac297053173b02b02a737d47f7b4a718e5b170ef,500,1/8/2019,20,10/10/2019,"Fixed" -0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,1/8/2019,90,20/10/2019,"Percentage" -0x10223927009b8add0960359dd90d1449415b7ca9,25,1/8/2019,30,10/10/2019,"Fixed" \ No newline at end of file +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,8/1/2019,90,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,30,10/10/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,20,10/10/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/1/2019,90,20/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,25,8/1/2019,30,10/10/2019,"Fixed" \ No newline at end of file From ceb6acf54c92c97ba4dfb0998db4e0bc26b98b7a Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 14:17:12 -0300 Subject: [PATCH 10/17] CLI - Changed names of restrictions --- CLI/commands/transfer_manager.js | 387 +++++++++--------- ...ta.csv => add_custom_restriction_data.csv} | 0 ...csv => modify_custom_restriction_data.csv} | 0 ...csv => remove_custom_restriction_data.csv} | 0 4 files changed, 194 insertions(+), 193 deletions(-) rename CLI/data/Transfer/VRTM/{add_restriction_data.csv => add_custom_restriction_data.csv} (100%) rename CLI/data/Transfer/VRTM/{modify_restriction_data.csv => modify_custom_restriction_data.csv} (100%) rename CLI/data/Transfer/VRTM/{remove_restriction_data.csv => remove_custom_restriction_data.csv} (100%) diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 929d43017..8b84e74f8 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -23,9 +23,9 @@ const REVOKE_MANUAL_APPROVAL_DATA_CSV = `${__dirname}/../data/Transfer/MATM/revo const ADD_DAILY_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/add_daily_restriction_data.csv`; const MODIFY_DAILY_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/modify_daily_restriction_data.csv`; const REMOVE_DAILY_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/remove_daily_restriction_data.csv`; -const ADD_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/add_restriction_data.csv`; -const MODIFY_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/modify_restriction_data.csv`; -const REMOVE_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/remove_restriction_data.csv`; +const ADD_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/add_custom_restriction_data.csv`; +const MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/modify_custom_restriction_data.csv`; +const REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/remove_custom_restriction_data.csv`; const RESTRICTION_TYPES = ['Fixed', 'Percentage']; @@ -75,6 +75,8 @@ async function executeApp() { 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); @@ -82,16 +84,14 @@ async function executeApp() { limitMessage: "Must be a valid address", defaultInput: Issuer.address }); - let verifyFromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(verifyTransferFrom).call()); - console.log(chalk.yellow(`Balance of ${verifyTransferFrom}: ${verifyFromBalance} ${tokenSymbol}`)); + 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", }); - let verifyToBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(verifyTransferTo).call()); - console.log(chalk.yellow(`Balance of ${verifyTransferTo}: ${verifyToBalance} ${tokenSymbol}`)); + 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) { @@ -101,16 +101,16 @@ async function executeApp() { } break; case 'Transfer': - let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(Issuer.address).call()); - console.log(chalk.yellow(`Balance of ${Issuer.address}: ${fromBalance} ${tokenSymbol}`)); + 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" }); - let toBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(transferTo).call()); - console.log(chalk.yellow(`Balance of ${transferTo}: ${toBalance} ${tokenSymbol}`)); + 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) { @@ -118,8 +118,9 @@ async function executeApp() { 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!`)); - console.log(`Balance of ${Issuer.address} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(Issuer.address).call())} ${tokenSymbol}`); - console.log(`Balance of ${transferTo} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(transferTo).call())} ${tokenSymbol}`); + 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.`)); } @@ -1523,26 +1524,26 @@ async function removeInvestorsFromBlacklistsInBatch() { async function volumeRestrictionTM() { console.log('\n', chalk.blue(`Volume Restriction Transfer Manager at ${currentTransferManager.options.address}`, '\n')); - let defaultDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); - let hasDefaultDailyRestriction = parseInt(defaultDailyRestriction.startTime) !== 0; - let defaultRestriction = await currentTransferManager.methods.defaultRestriction().call(); - let hasDefaultRestriction = parseInt(defaultRestriction.startTime) !== 0; + let globalDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); + let hasGlobalDailyRestriction = parseInt(globalDailyRestriction.startTime) !== 0; + let globalCustomRestriction = await currentTransferManager.methods.defaultRestriction().call(); + let hasGlobalCustomRestriction = parseInt(globalCustomRestriction.startTime) !== 0; - console.log(`- Default daily restriction: ${hasDefaultDailyRestriction ? '' : 'None'}`); - if (hasDefaultDailyRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[defaultDailyRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${defaultDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(defaultDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(defaultDailyRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(defaultDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${defaultDailyRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(defaultDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(`- Default daily restriction: ${hasGlobalDailyRestriction ? '' : 'None'}`); + if (hasGlobalDailyRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[globalDailyRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${globalDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalDailyRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(globalDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${globalDailyRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(globalDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Default restriction: ${hasDefaultRestriction ? '' : 'None'}`); - if (hasDefaultRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[defaultRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${defaultRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(defaultRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(defaultRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(defaultRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${defaultRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(defaultRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(`- Default custom restriction: ${hasGlobalCustomRestriction ? '' : 'None'}`); + if (hasGlobalCustomRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[globalCustomRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${globalCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalCustomRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(globalCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${globalCustomRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(globalCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedData().call(); @@ -1550,7 +1551,7 @@ async function volumeRestrictionTM() { let options = []; if (addressesAndRestrictions[0].length > 0) { - options.push('Show restrictios'); + options.push('Show restrictions'); } options.push( 'Change exempt wallet', @@ -1578,7 +1579,7 @@ async function volumeRestrictionTM() { await changeExemptWallet(); break; case 'Change default restrictions': - await changeDefaultRestrictions(hasDefaultDailyRestriction, hasDefaultRestriction); + await changeDefaultRestrictions(hasGlobalDailyRestriction, hasGlobalCustomRestriction); break; case 'Change individual restrictions': await changeIndividualRestrictions(); @@ -1644,85 +1645,85 @@ async function changeExemptWallet() { console.log(chalk.green(`${changeExemptWalletEvent._wallet} has been ${changeExemptWalletEvent._change ? `added to` : `removed from`} exempt wallets successfully!`)); } -async function changeDefaultRestrictions(hasDefaultDailyRestriction, hasDefaultRestriction) { +async function changeDefaultRestrictions(hasGlobalDailyRestriction, hasGlobalCustomRestriction) { let options = []; - if (!hasDefaultDailyRestriction) { - options.push('Add default daily restriction'); + if (!hasGlobalDailyRestriction) { + options.push('Add global daily restriction'); } else { - options.push('Modify default daily restriction', 'Remove default daily restriction'); + options.push('Modify global daily restriction', 'Remove global daily restriction'); } - if (!hasDefaultRestriction) { - options.push('Add default restriction'); + if (!hasGlobalCustomRestriction) { + options.push('Add global custom restriction'); } else { - options.push('Modify default restriction', 'Remove default restriction'); + options.push('Modify global custom restriction', 'Remove global custom restriction'); } 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 default daily restriction': - let defaultDailyRestrictoToAdd = inputRestrictionData(true); - let addDefaultDailyRestrictionAction = currentTransferManager.methods.addDefaultDailyRestriction( - defaultDailyRestrictoToAdd.allowedTokens, - defaultDailyRestrictoToAdd.startTime, - defaultDailyRestrictoToAdd.endTime, - defaultDailyRestrictoToAdd.restrictionType + case 'Add global daily restriction': + let globalDailyRestrictoToAdd = inputRestrictionData(true); + let addGlobalDailyRestrictionAction = currentTransferManager.methods.addDefaultDailyRestriction( + globalDailyRestrictoToAdd.allowedTokens, + globalDailyRestrictoToAdd.startTime, + globalDailyRestrictoToAdd.endTime, + globalDailyRestrictoToAdd.restrictionType ); - let addDefaultDailyRestrictionReceipt = await common.sendTransaction(addDefaultDailyRestrictionAction); - let addDefaultDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addDefaultDailyRestrictionReceipt.logs, 'AddDefaultDailyRestriction'); - console.log(chalk.green(`Daily default restriction has been added successfully!`)); - break; - case 'Modify default daily restriction': - let defaultDailyRestrictoToModify = inputRestrictionData(true); - let modifyDefaultDailyRestrictionAction = currentTransferManager.methods.modifyDefaultDailyRestriction( - defaultDailyRestrictoToModify.allowedTokens, - defaultDailyRestrictoToModify.startTime, - defaultDailyRestrictoToModify.endTime, - defaultDailyRestrictoToModify.restrictionType + let addGlobalDailyRestrictionReceipt = await common.sendTransaction(addGlobalDailyRestrictionAction); + let addGlobalDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addGlobalDailyRestrictionReceipt.logs, 'AddDefaultDailyRestriction'); + console.log(chalk.green(`Global daily restriction has been added successfully!`)); + break; + case 'Modify global daily restriction': + let globalDailyRestrictoToModify = inputRestrictionData(true); + let modifyGlobalDailyRestrictionAction = currentTransferManager.methods.modifyDefaultDailyRestriction( + globalDailyRestrictoToModify.allowedTokens, + globalDailyRestrictoToModify.startTime, + globalDailyRestrictoToModify.endTime, + globalDailyRestrictoToModify.restrictionType ); - let modifyDefaultDailyRestrictionReceipt = await common.sendTransaction(modifyDefaultDailyRestrictionAction); - let modifyDefaultDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyDefaultDailyRestrictionReceipt.logs, 'ModifyDefaultDailyRestriction'); - console.log(chalk.green(`Daily default restriction has been modified successfully!`)); - break; - case 'Remove default daily restriction': - let removeDefaultDailyRestrictionAction = currentTransferManager.methods.removeDefaultDailyRestriction(); - let removeDefaultDailyRestrictionReceipt = await common.sendTransaction(removeDefaultDailyRestrictionAction); - let removeDefaultDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeDefaultDailyRestrictionReceipt.logs, 'DefaultDailyRestrictionRemoved'); - console.log(chalk.green(`Daily default restriction has been removed successfully!`)); - break; - case 'Add default restriction': - let defaultRestrictoToAdd = inputRestrictionData(false); - let addDefaultRestrictionAction = currentTransferManager.methods.addDefaultRestriction( - defaultRestrictoToAdd.allowedTokens, - defaultRestrictoToAdd.startTime, - defaultRestrictoToAdd.rollingPeriodInDays, - defaultRestrictoToAdd.endTime, - defaultRestrictoToAdd.restrictionType + let modifyGlobalDailyRestrictionReceipt = await common.sendTransaction(modifyGlobalDailyRestrictionAction); + let modifyGlobalDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyGlobalDailyRestrictionReceipt.logs, 'ModifyDefaultDailyRestriction'); + console.log(chalk.green(`Global daily restriction has been modified successfully!`)); + break; + case 'Remove global daily restriction': + let removeGlobalDailyRestrictionAction = currentTransferManager.methods.removeDefaultDailyRestriction(); + let removeGlobalDailyRestrictionReceipt = await common.sendTransaction(removeGlobalDailyRestrictionAction); + let removeGlobalDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeGlobalDailyRestrictionReceipt.logs, 'DefaultDailyRestrictionRemoved'); + console.log(chalk.green(`Global daily restriction has been removed successfully!`)); + break; + case 'Add global custom restriction': + let globalCustomRestrictoToAdd = inputRestrictionData(false); + let globalCustomRestrictionAction = currentTransferManager.methods.addDefaultRestriction( + globalCustomRestrictoToAdd.allowedTokens, + globalCustomRestrictoToAdd.startTime, + globalCustomRestrictoToAdd.rollingPeriodInDays, + globalCustomRestrictoToAdd.endTime, + globalCustomRestrictoToAdd.restrictionType ); - let addDefaultRestrictionReceipt = await common.sendTransaction(addDefaultRestrictionAction); - let addDefaultRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addDefaultRestrictionReceipt.logs, 'AddDefaultRestriction'); - console.log(chalk.green(`Default restriction has been added successfully!`)); - break; - case 'Modify default restriction': - let defaultRestrictoToModify = inputRestrictionData(false); - let modifyDefaultRestrictionAction = currentTransferManager.methods.modifyDefaultRestriction( - defaultRestrictoToModify.allowedTokens, - defaultRestrictoToModify.startTime, - defaultRestrictoToModify.rollingPeriodInDays, - defaultRestrictoToModify.endTime, - defaultRestrictoToModify.restrictionType + let globalCustomRestrictionReceipt = await common.sendTransaction(globalCustomRestrictionAction); + let globalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, globalCustomRestrictionReceipt.logs, 'AddDefaultRestriction'); + console.log(chalk.green(`Global custom restriction has been added successfully!`)); + break; + case 'Modify global custom restriction': + let globalCustomRestrictoToModify = inputRestrictionData(false); + let globalCustomRestrictionAction = currentTransferManager.methods.modifyDefaultRestriction( + globalCustomRestrictoToModify.allowedTokens, + globalCustomRestrictoToModify.startTime, + globalCustomRestrictoToModify.rollingPeriodInDays, + globalCustomRestrictoToModify.endTime, + globalCustomRestrictoToModify.restrictionType ); - let modifyDefaultRestrictionReceipt = await common.sendTransaction(modifyDefaultRestrictionAction); - let modifyDefaultRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyDefaultRestrictionReceipt.logs, 'ModifyDefaultRestriction'); - console.log(chalk.green(`Default restriction has been modified successfully!`)); + let globalCustomRestrictionReceipt = await common.sendTransaction(globalCustomRestrictionAction); + let globalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, globalCustomRestrictionReceipt.logs, 'ModifyDefaultRestriction'); + console.log(chalk.green(`Global custom restriction has been modified successfully!`)); break; - case 'Remove default restriction': - let removeDefaultRestrictionAction = currentTransferManager.methods.removeDefaultRestriction(); - let removeDefaultRestrictionReceipt = await common.sendTransaction(removeDefaultRestrictionAction); - let removeDefaultRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeDefaultRestrictionReceipt.logs, 'DefaultRestrictionRemoved'); - console.log(chalk.green(`Default restriction has been removed successfully!`)); + case 'Remove global custom restriction': + let removeGlobalCustomRestrictionAction = currentTransferManager.methods.removeDefaultRestriction(); + let removeGlobalCustomRestrictionReceipt = await common.sendTransaction(removeGlobalCustomRestrictionAction); + let removeGlobalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeGlobalCustomRestrictionReceipt.logs, 'DefaultRestrictionRemoved'); + console.log(chalk.green(`Global custom restriction has been removed successfully!`)); break; } } @@ -1737,8 +1738,8 @@ async function changeIndividualRestrictions() { let currentDailyRestriction = await currentTransferManager.methods.individualDailyRestriction(holder).call(); let hasDailyRestriction = parseInt(currentDailyRestriction.startTime) !== 0; - let currentRestriction = await currentTransferManager.methods.individualRestriction(holder).call(); - let hasRestriction = parseInt(currentRestriction.startTime) !== 0; + let currentCustomRestriction = await currentTransferManager.methods.individualRestriction(holder).call(); + let hasCustomRestriction = parseInt(currentCustomRestriction.startTime) !== 0; console.log(`*** Current individual restrictions for ${holder} ***`, '\n'); @@ -1750,13 +1751,13 @@ async function changeIndividualRestrictions() { console.log(` Rolling period: ${currentDailyRestriction.rollingPeriodInDays} days`); console.log(` End time: ${moment.unix(currentDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Other restriction: ${hasRestriction ? '' : 'None'} `); - if (hasRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[currentRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${currentRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(currentRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${currentRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(currentRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(`- Other restriction: ${hasCustomRestriction ? '' : 'None'} `); + if (hasCustomRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[currentCustomRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${currentCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentCustomRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(currentCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${currentCustomRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(currentCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } let options = []; @@ -1766,10 +1767,10 @@ async function changeIndividualRestrictions() { options.push('Modify individual daily restriction', 'Remove individual daily restriction'); } - if (!hasRestriction) { - options.push('Add individual restriction'); + if (!hasCustomRestriction) { + options.push('Add individual custom restriction'); } else { - options.push('Modify individual restriction', 'Remove individual restriction'); + options.push('Modify individual custom restriction', 'Remove individual custom restriction'); } let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); @@ -1777,26 +1778,26 @@ async function changeIndividualRestrictions() { console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { case 'Add individual daily restriction': - let dailyRestrictoToAdd = inputRestrictionData(true); + let dailyRestrictonToAdd = inputRestrictionData(true); let addDailyRestrictionAction = currentTransferManager.methods.addIndividualDailyRestriction( holder, - dailyRestrictoToAdd.allowedTokens, - dailyRestrictoToAdd.startTime, - dailyRestrictoToAdd.endTime, - dailyRestrictoToAdd.restrictionType + dailyRestrictonToAdd.allowedTokens, + dailyRestrictonToAdd.startTime, + dailyRestrictonToAdd.endTime, + dailyRestrictonToAdd.restrictionType ); let addDailyRestrictionReceipt = await common.sendTransaction(addDailyRestrictionAction); let addDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addDailyRestrictionReceipt.logs, 'AddIndividualDailyRestriction'); console.log(chalk.green(`Daily restriction for ${addDailyRestrictionEvent._holder} has been added successfully!`)); break; case 'Modify individual daily restriction': - let dailyRestrictoToModify = inputRestrictionData(true); + let dailyRestrictonToModify = inputRestrictionData(true); let modifyDailyRestrictionAction = currentTransferManager.methods.modifyIndividualDailyRestriction( holder, - dailyRestrictoToModify.allowedTokens, - dailyRestrictoToModify.startTime, - dailyRestrictoToModify.endTime, - dailyRestrictoToModify.restrictionType + dailyRestrictonToModify.allowedTokens, + dailyRestrictonToModify.startTime, + dailyRestrictonToModify.endTime, + dailyRestrictonToModify.restrictionType ); let modifyDailyRestrictionReceipt = await common.sendTransaction(modifyDailyRestrictionAction); let modifyDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyDailyRestrictionReceipt.logs, 'ModifyIndividualDailyRestriction'); @@ -1808,39 +1809,39 @@ async function changeIndividualRestrictions() { let removeDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeDailyRestrictionReceipt.logs, 'IndividualDailyRestrictionRemoved'); console.log(chalk.green(`Daily restriction for ${removeDailyRestrictionEvent._holder} has been removed successfully!`)); break; - case 'Add individual restriction': - let restrictoToAdd = inputRestrictionData(false); - let addRestrictionAction = currentTransferManager.methods.addIndividualRestriction( + case 'Add individual custom restriction': + let restrictonToAdd = inputRestrictionData(false); + let addCustomRestrictionAction = currentTransferManager.methods.addIndividualRestriction( holder, - restrictoToAdd.allowedTokens, - restrictoToAdd.startTime, - restrictoToAdd.rollingPeriodInDays, - restrictoToAdd.endTime, - restrictoToAdd.restrictionType + restrictonToAdd.allowedTokens, + restrictonToAdd.startTime, + restrictonToAdd.rollingPeriodInDays, + restrictonToAdd.endTime, + restrictonToAdd.restrictionType ); - let addRestrictionReceipt = await common.sendTransaction(addRestrictionAction); - let addRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addRestrictionReceipt.logs, 'AddIndividualRestriction'); - console.log(chalk.green(`Restriction for ${addRestrictionEvent._holder} has been added successfully!`)); + let addCustomRestrictionReceipt = await common.sendTransaction(addCustomRestrictionAction); + let addCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addCustomRestrictionReceipt.logs, 'AddIndividualRestriction'); + console.log(chalk.green(`Custom restriction for ${addCustomRestrictionEvent._holder} has been added successfully!`)); break; - case 'Modify individual restriction': - let restrictoToModify = inputRestrictionData(false); - let modifyRestrictionAction = currentTransferManager.methods.modifyIndividualRestriction( + case 'Modify individual custom restriction': + let restrictonToModify = inputRestrictionData(false); + let modifyCustomRestrictionAction = currentTransferManager.methods.modifyIndividualRestriction( holder, - restrictoToModify.allowedTokens, - restrictoToModify.startTime, - restrictoToModify.rollingPeriodInDays, - restrictoToModify.endTime, - restrictoToModify.restrictionType + restrictonToModify.allowedTokens, + restrictonToModify.startTime, + restrictonToModify.rollingPeriodInDays, + restrictonToModify.endTime, + restrictonToModify.restrictionType ); - let modifyRestrictionReceipt = await common.sendTransaction(modifyRestrictionAction); - let modifyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyRestrictionReceipt.logs, 'ModifyIndividualRestriction'); - console.log(chalk.green(`Restriction for ${modifyRestrictionEvent._holder} has been modified successfully!`)); + let modifyCustomRestrictionReceipt = await common.sendTransaction(modifyCustomRestrictionAction); + let modifyCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyCustomRestrictionReceipt.logs, 'ModifyIndividualRestriction'); + console.log(chalk.green(`Custom restriction for ${modifyCustomRestrictionEvent._holder} has been modified successfully!`)); break; - case 'Remove individual restriction': - let removeRestrictionAction = currentTransferManager.methods.removeIndividualRestriction(holder); - let removeRestrictionReceipt = await common.sendTransaction(removeRestrictionAction); - let removeRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeRestrictionReceipt.logs, 'IndividualRestrictionRemoved'); - console.log(chalk.green(`Restriction for ${removeRestrictionEvent._holder} has been removed successfully!`)); + case 'Remove individual custom restriction': + let removeCustomRestrictionAction = currentTransferManager.methods.removeIndividualRestriction(holder); + let removeCustomRestrictionReceipt = await common.sendTransaction(removeCustomRestrictionAction); + let removeCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeCustomRestrictionReceipt.logs, 'IndividualRestrictionRemoved'); + console.log(chalk.green(`Custom restriction for ${removeCustomRestrictionEvent._holder} has been removed successfully!`)); break; case 'RETURN': return; @@ -1855,54 +1856,54 @@ async function exploreAccount() { limitMessage: "Must be a valid address" }); - let appliyngdDailyRestriction = null; - let applyingOtherRestriction = null; + let appliyngDailyRestriction = null; + let applyingCustomRestriction = null; let hasIndividualRestrictions = false; let isExempted = await currentTransferManager.methods.exemptList(account).call(); if (!isExempted) { - let indiviaulDailyRestriction = await currentTransferManager.methods.individualDailyRestriction(account).call(); - if (parseInt(indiviaulDailyRestriction.endTime) !== 0) { - appliyngdDailyRestriction = indiviaulDailyRestriction; + let individuallDailyRestriction = await currentTransferManager.methods.individualDailyRestriction(account).call(); + if (parseInt(individuallDailyRestriction.endTime) !== 0) { + appliyngDailyRestriction = individuallDailyRestriction; } - let otherRestriction = await currentTransferManager.methods.individualRestriction(account).call(); - if (parseInt(otherRestriction.endTime) !== 0) { - applyingOtherRestriction = otherRestriction; + let customRestriction = await currentTransferManager.methods.individualRestriction(account).call(); + if (parseInt(customRestriction.endTime) !== 0) { + applyingCustomRestriction = customRestriction; } - hasIndividualRestrictions = applyingOtherRestriction || appliyngdDailyRestriction; + hasIndividualRestrictions = applyingCustomRestriction || appliyngDailyRestriction; if (!hasIndividualRestrictions) { - let defaultDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); - if (parseInt(defaultDailyRestriction.endTime) !== 0) { - appliyngdDailyRestriction = defaultDailyRestriction; + let globalDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); + if (parseInt(globalDailyRestriction.endTime) !== 0) { + appliyngDailyRestriction = globalDailyRestriction; } - let defaultOtherRestriction = await currentTransferManager.methods.defaultRestriction().call(); - if (parseInt(defaultOtherRestriction.endTime) === 0) { - applyingOtherRestriction = defaultOtherRestriction; + let globalCustomRestriction = await currentTransferManager.methods.defaultRestriction().call(); + if (parseInt(globalCustomRestriction.endTime) === 0) { + applyingCustomRestriction = globalCustomRestriction; } } } console.log(`*** Applying restrictions for ${account} ***`, '\n'); - console.log(`- Daily restriction: ${appliyngdDailyRestriction ? (!hasIndividualRestrictions ? 'default' : '') : 'None'}`); - if (appliyngdDailyRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[appliyngdDailyRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${appliyngdDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(appliyngdDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(appliyngdDailyRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(appliyngdDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${appliyngdDailyRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(appliyngdDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(`- Daily restriction: ${appliyngDailyRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'}`); + if (appliyngDailyRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[appliyngDailyRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${appliyngDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(appliyngDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(appliyngDailyRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(appliyngDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${appliyngDailyRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(appliyngDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Other restriction: ${applyingOtherRestriction ? (!hasIndividualRestrictions ? 'default' : '') : 'None'} `); - if (applyingOtherRestriction) { - console.log(` Type: ${RESTRICTION_TYPES[applyingOtherRestriction.typeOfRestriction]}`); - console.log(` Allowed tokens: ${applyingOtherRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(applyingOtherRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(applyingOtherRestriction.allowedTokens)}%`}`); - console.log(` Start time: ${moment.unix(applyingOtherRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); - console.log(` Rolling period: ${applyingOtherRestriction.rollingPeriodInDays} days`); - console.log(` End time: ${moment.unix(applyingOtherRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + console.log(`- Other restriction: ${applyingCustomRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'} `); + if (applyingCustomRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[applyingCustomRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${applyingCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(applyingCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(applyingCustomRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(applyingCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${applyingCustomRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(applyingCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - if (applyingOtherRestriction || appliyngdDailyRestriction) { + if (applyingCustomRestriction || appliyngDailyRestriction) { let bucketDetails; if (hasIndividualRestrictions) { bucketDetails = await currentTransferManager.methods.getIndividualBucketDetailsToUser(account).call(); @@ -1945,13 +1946,13 @@ async function operateWithMultipleRestrictions() { await removeDailyRestrictionsInBatch(); break; case 'Add multiple individual restrictions': - await addRestrictionsInBatch(); + await addCustomRestrictionsInBatch(); break; case 'Modify multiple individual restrictions': - await modifyRestrictionsInBatch(); + await modifyCustomRestrictionsInBatch(); break; case 'Remove multiple individual restrictions': - await removeRestrictionsInBatch(); + await removeCustomRestrictionsInBatch(); break; } } @@ -2054,9 +2055,9 @@ async function removeDailyRestrictionsInBatch() { } } -async function addRestrictionsInBatch() { - let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_RESTRICTIONS_DATA_CSV}): `, { - defaultInput: ADD_RESTRICTIONS_DATA_CSV +async function addCustomRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: ADD_CUSTOM_RESTRICTIONS_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) { @@ -2080,19 +2081,19 @@ async function addRestrictionsInBatch() { let batches = common.splitIntoBatches(validData, batchSize); let [holderArray, allowanceArray, startTimeArray, rollingPeriodArray, endTimeArray, restrictionTypeArray] = common.transposeBatches(batches); for (let batch = 0; batch < batches.length; batch++) { - console.log(`Batch ${batch + 1} - Attempting to add restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + console.log(`Batch ${batch + 1} - Attempting to add custom restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); allowanceArray[batch] = allowanceArray[batch].map(n => web3.utils.toWei(n.toString())); restrictionTypeArray[batch] = restrictionTypeArray[batch].map(n => RESTRICTION_TYPES.indexOf(n)); let action = currentTransferManager.methods.addIndividualRestrictionMulti(holderArray[batch], allowanceArray[batch], startTimeArray[batch], rollingPeriodArray[batch], endTimeArray[batch], restrictionTypeArray[batch]); let receipt = await common.sendTransaction(action); - console.log(chalk.green('Add multiple restrictions transaction was successful.')); + console.log(chalk.green('Add multiple custom restrictions 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 modifyRestrictionsInBatch() { - let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_RESTRICTIONS_DATA_CSV}): `, { - defaultInput: MODIFY_RESTRICTIONS_DATA_CSV +async function modifyCustomRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: MODIFY_CUSTOM_RESTRICTIONS_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) { @@ -2116,19 +2117,19 @@ async function modifyRestrictionsInBatch() { let batches = common.splitIntoBatches(validData, batchSize); let [holderArray, allowanceArray, startTimeArray, rollingPeriodArray, endTimeArray, restrictionTypeArray] = common.transposeBatches(batches); for (let batch = 0; batch < batches.length; batch++) { - console.log(`Batch ${batch + 1} - Attempting to modify restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + console.log(`Batch ${batch + 1} - Attempting to modify custom restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); allowanceArray[batch] = allowanceArray[batch].map(n => web3.utils.toWei(n.toString())); restrictionTypeArray[batch] = restrictionTypeArray[batch].map(n => RESTRICTION_TYPES.indexOf(n)); let action = currentTransferManager.methods.modifyIndividualRestrictionMulti(holderArray[batch], allowanceArray[batch], startTimeArray[batch], rollingPeriodArray[batch], endTimeArray[batch], restrictionTypeArray[batch]); let receipt = await common.sendTransaction(action); - console.log(chalk.green('Modify multiple restrictions transaction was successful.')); + console.log(chalk.green('Modify multiple custom restrictions 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 removeRestrictionsInBatch() { - let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_RESTRICTIONS_DATA_CSV}): `, { - defaultInput: REMOVE_RESTRICTIONS_DATA_CSV +async function removeCustomRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: REMOVE_CUSTOM_RESTRICTIONS_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) { @@ -2146,10 +2147,10 @@ async function removeRestrictionsInBatch() { let batches = common.splitIntoBatches(validData, batchSize); let [holderArray] = common.transposeBatches(batches); for (let batch = 0; batch < batches.length; batch++) { - console.log(`Batch ${batch + 1} - Attempting to remove restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + console.log(`Batch ${batch + 1} - Attempting to remove custom restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); let action = currentTransferManager.methods.removeIndividualRestrictionMulti(holderArray[batch]); let receipt = await common.sendTransaction(action); - console.log(chalk.green('Remove multiple restrictions transaction was successful.')); + console.log(chalk.green('Remove multiple custom restrictions 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`); } } diff --git a/CLI/data/Transfer/VRTM/add_restriction_data.csv b/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv similarity index 100% rename from CLI/data/Transfer/VRTM/add_restriction_data.csv rename to CLI/data/Transfer/VRTM/add_custom_restriction_data.csv diff --git a/CLI/data/Transfer/VRTM/modify_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv similarity index 100% rename from CLI/data/Transfer/VRTM/modify_restriction_data.csv rename to CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv diff --git a/CLI/data/Transfer/VRTM/remove_restriction_data.csv b/CLI/data/Transfer/VRTM/remove_custom_restriction_data.csv similarity index 100% rename from CLI/data/Transfer/VRTM/remove_restriction_data.csv rename to CLI/data/Transfer/VRTM/remove_custom_restriction_data.csv From d2636c0a8172a71a711a054f10b3b90cd59d158d Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 14:17:56 -0300 Subject: [PATCH 11/17] Minor fix --- CLI/commands/transfer_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 8b84e74f8..bfc47ff3d 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -2296,7 +2296,7 @@ async function selectToken() { case 'Enter token symbol manually': result = readlineSync.question('Enter the token symbol: '); break; - case 'Exit': + case 'EXIT': process.exit(); break; default: From 60efa3f6cf8116fe67e0d5adbdf20c86ba0a1a53 Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 15:03:11 -0300 Subject: [PATCH 12/17] CLI - Percentage with decimals fix --- CLI/commands/transfer_manager.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index bfc47ff3d..e8fce26c8 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -1537,7 +1537,7 @@ async function volumeRestrictionTM() { console.log(` Rolling period: ${globalDailyRestriction.rollingPeriodInDays} days`); console.log(` End time: ${moment.unix(globalDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Default custom restriction: ${hasGlobalCustomRestriction ? '' : 'None'}`); + console.log(`- Default custom restriction: ${hasGlobalCustomRestriction ? '' : 'None'}`); if (hasGlobalCustomRestriction) { console.log(` Type: ${RESTRICTION_TYPES[globalCustomRestriction.typeOfRestriction]}`); console.log(` Allowed tokens: ${globalCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalCustomRestriction.allowedTokens)}%`}`); @@ -1565,7 +1565,7 @@ async function volumeRestrictionTM() { let optionSelected = index !== -1 ? options[index] : 'RETURN'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { - case 'Show restrictios': + case 'Show restrictions': showRestrictionTable( addressesAndRestrictions.allAddresses, addressesAndRestrictions.allowedTokens, @@ -1695,28 +1695,28 @@ async function changeDefaultRestrictions(hasGlobalDailyRestriction, hasGlobalCus break; case 'Add global custom restriction': let globalCustomRestrictoToAdd = inputRestrictionData(false); - let globalCustomRestrictionAction = currentTransferManager.methods.addDefaultRestriction( + let addGlobalCustomRestrictionAction = currentTransferManager.methods.addDefaultRestriction( globalCustomRestrictoToAdd.allowedTokens, globalCustomRestrictoToAdd.startTime, globalCustomRestrictoToAdd.rollingPeriodInDays, globalCustomRestrictoToAdd.endTime, globalCustomRestrictoToAdd.restrictionType ); - let globalCustomRestrictionReceipt = await common.sendTransaction(globalCustomRestrictionAction); - let globalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, globalCustomRestrictionReceipt.logs, 'AddDefaultRestriction'); + let addGlobalCustomRestrictionReceipt = await common.sendTransaction(addGlobalCustomRestrictionAction); + let addGlobalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addGlobalCustomRestrictionReceipt.logs, 'AddDefaultRestriction'); console.log(chalk.green(`Global custom restriction has been added successfully!`)); break; case 'Modify global custom restriction': let globalCustomRestrictoToModify = inputRestrictionData(false); - let globalCustomRestrictionAction = currentTransferManager.methods.modifyDefaultRestriction( + let modifiyGlobalCustomRestrictionAction = currentTransferManager.methods.modifyDefaultRestriction( globalCustomRestrictoToModify.allowedTokens, globalCustomRestrictoToModify.startTime, globalCustomRestrictoToModify.rollingPeriodInDays, globalCustomRestrictoToModify.endTime, globalCustomRestrictoToModify.restrictionType ); - let globalCustomRestrictionReceipt = await common.sendTransaction(globalCustomRestrictionAction); - let globalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, globalCustomRestrictionReceipt.logs, 'ModifyDefaultRestriction'); + let modifyGlobalCustomRestrictionReceipt = await common.sendTransaction(modifiyGlobalCustomRestrictionAction); + let modifyGlobalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyGlobalCustomRestrictionReceipt.logs, 'ModifyDefaultRestriction'); console.log(chalk.green(`Global custom restriction has been modified successfully!`)); break; case 'Remove global custom restriction': @@ -1743,7 +1743,7 @@ async function changeIndividualRestrictions() { console.log(`*** Current individual restrictions for ${holder} ***`, '\n'); - console.log(`- Daily restriction: ${hasDailyRestriction ? '' : 'None'}`); + console.log(`- Daily restriction: ${hasDailyRestriction ? '' : 'None'}`); if (hasDailyRestriction) { console.log(` Type: ${RESTRICTION_TYPES[currentDailyRestriction.typeOfRestriction]}`); console.log(` Allowed tokens: ${currentDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentDailyRestriction.allowedTokens)}%`}`); @@ -1751,7 +1751,7 @@ async function changeIndividualRestrictions() { console.log(` Rolling period: ${currentDailyRestriction.rollingPeriodInDays} days`); console.log(` End time: ${moment.unix(currentDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); } - console.log(`- Other restriction: ${hasCustomRestriction ? '' : 'None'} `); + console.log(`- Custom restriction: ${hasCustomRestriction ? '' : 'None'} `); if (hasCustomRestriction) { console.log(` Type: ${RESTRICTION_TYPES[currentCustomRestriction.typeOfRestriction]}`); console.log(` Allowed tokens: ${currentCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentCustomRestriction.allowedTokens)}%`}`); @@ -2159,9 +2159,9 @@ function inputRestrictionData(isDaily) { let restriction = {}; restriction.restrictionType = readlineSync.keyInSelect(RESTRICTION_TYPES, 'How do you want to set the allowance? ', { cancel: false }); if (restriction.restrictionType == RESTRICTION_TYPES.indexOf('Fixed')) { - restriction.allowedTokens = web3.utils.toWei(readlineSync.questionInt(`Enter the maximum amount of tokens allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); + restriction.allowedTokens = web3.utils.toWei(readlineSync.question(`Enter the maximum amount of tokens allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); } else { - restriction.allowedTokens = toWeiPercentage(readlineSync.questionInt(`Enter the maximum percentage of total supply allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); + restriction.allowedTokens = toWeiPercentage(readlineSync.question(`Enter the maximum percentage of total supply allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); } if (isDaily) { restriction.rollingPeriodInDays = 1; @@ -2197,11 +2197,11 @@ function signData(tmAddress, investorAddress, fromTime, toTime, expiryTime, rest */ function toWeiPercentage(number) { - return new web3.utils.BN(web3.utils.toWei(number)).divn(100); + return web3.utils.toWei((parseFloat(number) / 100).toString()); } function fromWeiPercentage(number) { - return web3.utils.fromWei(new web3.utils.BN(number).muln(100)); + return web3.utils.fromWei(new web3.utils.BN(number).muln(100)).toString(); } async function getAllModulesByType(type) { From 5c2771114cb77f8a37aac0552e545dbac80e7a2e Mon Sep 17 00:00:00 2001 From: Victor Date: Wed, 9 Jan 2019 14:17:12 -0300 Subject: [PATCH 13/17] CLI - Changed names of restrictions --- CLI/commands/helpers/contract_abis.js | 7 +- CLI/commands/transfer_manager.js | 425 +++++++- .../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 + .../LockupVolumeRestrictionTM.sol | 411 -------- .../TransferManager/LockUpTransferManager.sol | 636 +++++++++++ .../LockUpTransferManagerFactory.sol} | 20 +- migrations/2_deploy_contracts.js | 21 +- test/helpers/createInstances.js | 6 +- test/w_lockup_transfer_manager.js | 995 ++++++++++++++++++ ...kup_volume_restriction_transfer_manager.js | 808 -------------- .../z_fuzz_test_adding_removing_modules_ST.js | 2 +- test/z_general_permission_manager_fuzzer.js | 1 - 16 files changed, 2117 insertions(+), 1249 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 delete mode 100644 contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol create mode 100644 contracts/modules/TransferManager/LockUpTransferManager.sol rename contracts/modules/{Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol => TransferManager/LockUpTransferManagerFactory.sol} (74%) create mode 100644 test/w_lockup_transfer_manager.js delete mode 100644 test/w_lockup_volume_restriction_transfer_manager.js diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index f33e8bc28..67dc43244 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -11,6 +11,7 @@ let manualApprovalTransferManagerABI; let blacklistTransferManagerABI; let countTransferManagerABI; let percentageTransferManagerABI; +let lockUpTransferManagerABI; let volumeRestrictionTMABI; let generalPermissionManagerABI; let polyTokenABI; @@ -38,8 +39,9 @@ try { manualApprovalTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ManualApprovalTransferManager.json`).toString()).abi; countTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/CountTransferManager.json`).toString()).abi; percentageTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PercentageTransferManager.json`).toString()).abi; - blacklistTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/BlacklistTransferManager.json`).toString()).abi; + blacklistTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/BlacklistTransferManager.json`).toString()).abi; volumeRestrictionTMABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/VolumeRestrictionTM.json`).toString()).abi; + lockUpTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/LockUpTransferManager.json`).toString()).abi; generalPermissionManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/GeneralPermissionManager.json`).toString()).abi; polyTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolyTokenFaucet.json`).toString()).abi; cappedSTOFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/CappedSTOFactory.json`).toString()).abi; @@ -97,6 +99,9 @@ module.exports = { percentageTransferManager: function () { return percentageTransferManagerABI; }, + lockUpTransferManager: function () { + return lockUpTransferManagerABI; + }, volumeRestrictionTM: function () { return volumeRestrictionTMABI; }, diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index e8fce26c8..1ea70fa2b 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -6,7 +6,7 @@ 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 @@ -26,6 +26,11 @@ const REMOVE_DAILY_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/r const ADD_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/add_custom_restriction_data.csv`; const MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/modify_custom_restriction_data.csv`; const REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/remove_custom_restriction_data.csv`; +const ADD_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/add_lockup_data.csv`; +const MODIFY_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/modify_lockup_data.csv`; +const DELETE_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/delete_lockup_data.csv`; +const ADD_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/add_lockup_investor_data.csv`; +const REMOVE_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/remove_lockup_investor_data.csv`; const RESTRICTION_TYPES = ['Fixed', 'Percentage']; @@ -71,7 +76,7 @@ async function executeApp() { 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'; + let optionSelected = index != -1 ? options[index] : 'EXIT'; console.log('Selected:', optionSelected, '\n'); switch (optionSelected) { case 'Verify transfer': @@ -241,6 +246,11 @@ async function configExistingModules(tmModules) { currentTransferManager.setProvider(web3.currentProvider); await percentageTransferManager(); break; + case 'LockUpTransferManager': + currentTransferManager = new web3.eth.Contract(abis.lockUpTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await lockUpTransferManager(); + break; case 'BlacklistTransferManager': currentTransferManager = new web3.eth.Contract(abis.blacklistTransferManager(), tmModules[index].address); currentTransferManager.setProvider(web3.currentProvider); @@ -292,7 +302,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(); @@ -553,7 +563,7 @@ async function modifyWhitelistInBatch(_csvFilePath, _batchSize) { } 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 totalApprovals = await currentTransferManager.methods.getTotalApprovalsLength().call(); console.log(`- Current active approvals: ${totalApprovals}`); @@ -942,7 +952,7 @@ function getBinarySize(string) { } 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(); @@ -969,7 +979,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(); @@ -1069,15 +1079,13 @@ async function percentageTransferManager() { console.log(chalk.green(`Transactions which are part of the primary issuance will NOT be ignored!`)); } break; - case 'RETURN': - return; } await percentageTransferManager(); } async function blacklistTransferManager() { - console.log(chalk.blue(`Blacklist Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + console.log('\n', chalk.blue(`Blacklist Transfer Manager at ${currentTransferManager.options.address}`), '\n'); let currentBlacklists = await currentTransferManager.methods.getAllBlacklists().call(); console.log(`- Blacklists: ${currentBlacklists.length}`); @@ -2180,6 +2188,401 @@ function inputRestrictionData(isDaily) { return restriction; } +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, '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!`)); + } + 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.getLockUp(lockupName).call(); + let investors = await currentTransferManager.methods.getListOfAddresses(lockupName).call(); + + 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.endTime).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, '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): `, { + 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`); + } +} + /* // Copied from tests function signData(tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo, pk) { @@ -2291,7 +2694,7 @@ 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 selected = index != -1 ? options[index] : 'EXIT'; switch (selected) { case 'Enter token symbol manually': result = readlineSync.question('Enter the token symbol: '); @@ -2315,7 +2718,7 @@ async function logTotalInvestors() { 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 diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol deleted file mode 100644 index 80f44cdb6..000000000 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ /dev/null @@ -1,411 +0,0 @@ -pragma solidity ^0.4.24; - -import "./../../TransferManager/ITransferManager.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; - - -contract LockupVolumeRestrictionTM is ITransferManager { - - using SafeMath for uint256; - - // permission definition - bytes32 public constant ADMIN = "ADMIN"; - - // 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 - } - - // maps user addresses to an array of lockups for that user - mapping (address => LockUp[]) internal lockUps; - - event AddNewLockUp( - address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed addedIndex - ); - - event RemoveLockUp( - address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed removedIndex - ); - - event ModifyLockUp( - address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed modifiedIndex - ); - - /** - * @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 - * @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 - if (!paused && _from != address(0) && lockUps[_from].length != 0) { - // check if this transfer is valid - return _checkIfValidTransfer(_from, _amount, _isTransfer); - } - 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 _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)); - - emit AddNewLockUp( - _userAddress, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - 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 _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) { - 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, - "Input array length mismatch" - ); - - for (uint i = 0; i < _userAddresses.length; i++) { - addLockUp(_userAddresses[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i], _startTimes[i], _totalAmounts[i]); - } - - } - - /** - * @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 - */ - 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]; - - emit RemoveLockUp( - _userAddress, - toRemove.lockUpPeriodSeconds, - toRemove.releaseFrequencySeconds, - toRemove.startTime, - toRemove.totalAmount, - _lockUpIndex - ); - - if (_lockUpIndex < userLockUps.length - 1) { - // move the last element in the array into the index that is desired to be removed. - userLockUps[_lockUpIndex] = userLockUps[userLockUps.length - 1]; - } - // 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 _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 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) { - /*solium-disable-next-line security/no-block-members*/ - startTime = now; - } - - _checkLockUpParams(_lockUpPeriodSeconds, _releaseFrequencySeconds, _totalAmount); - - // Get the lockup from the master list and edit it - lockUps[_userAddress][_lockUpIndex] = LockUp( - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - lockUps[_userAddress][_lockUpIndex].alreadyWithdrawn - ); - - emit ModifyLockUp( - _userAddress, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - _lockUpIndex - ); - } - - /** - * @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) { - 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, - 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]; - return ( - userLockUp.lockUpPeriodSeconds, - userLockUp.releaseFrequencySeconds, - userLockUp.startTime, - userLockUp.totalAmount, - userLockUp.alreadyWithdrawn - ); - } - - /** - * @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 - */ - 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; - } - - 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 - return Result.VALID; - } - return Result.INVALID; - } - - - /** - * @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 - */ - 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"); - - // check that the total amount to be released isn't too granular - require( - totalAmount % 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" - ); - - // 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); - require( - amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, - "The amount to be released per period is more granular than allowed by the token" - ); - } - - /** - * @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..993bbd5bb --- /dev/null +++ b/contracts/modules/TransferManager/LockUpTransferManager.sol @@ -0,0 +1,636 @@ +pragma solidity ^0.4.24; + +import "./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 AddLockUpToUser( + 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 a specific element in a user's lockups array given the user's address and the element index + * @param _lockupName The name of the lockup + */ + function getLockUp(bytes32 _lockupName) external view returns ( + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + uint256 unlockedAmount + ) { + 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)); + } + + /** + * @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); + // Find out the unlocked amount for a given lockup + uint256 unLockedAmount = (lockups[_lockupName].lockupAmount.mul(elapsedPeriod)).div(noOfPeriods); + 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*/ + uint256 startTime = _startTime; + + if (_startTime == 0) { + startTime = now; + } + require(startTime >= now, "Invalid start time"); + require(lockups[_lockupName].lockupAmount != 0, "Doesn't exist"); + + _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 AddLockUpToUser(_userAddress, _lockupName); + } + + function _addNewLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + uint256 startTime = _startTime; + require(_lockupName != bytes32(0), "Invalid name"); + require(lockups[_lockupName].lockupAmount == 0, "Already exist"); + /*solium-disable-next-line security/no-block-members*/ + if (_startTime == 0) { + startTime = now; + } + require(startTime >= now, "Invalid start time"); + _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 + pure + { + require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); + require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); + require(_lockupAmount != 0, "lockupAmount cannot be zero"); + } + + /** + * @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/LockupVolumeRestrictionTMFactory.sol b/contracts/modules/TransferManager/LockUpTransferManagerFactory.sol similarity index 74% rename from contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol rename to contracts/modules/TransferManager/LockUpTransferManagerFactory.sol index 73bd753f1..3f5e7b31a 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol +++ b/contracts/modules/TransferManager/LockUpTransferManagerFactory.sol @@ -1,12 +1,12 @@ pragma solidity ^0.4.24; -import "./../../ModuleFactory.sol"; -import "./LockupVolumeRestrictionTM.sol"; +import "../ModuleFactory.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, setupCost, now); - return address(lockupVolumeRestrictionTransferManager); + emit GenerateModuleFromFactory(address(lockUpTransferManager), getName(), address(this), msg.sender, setupCost, 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/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index a89be3b90..e9cdee650 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -9,6 +9,7 @@ const EtherDividendCheckpointLogic = artifacts.require('./EtherDividendCheckpoin const ERC20DividendCheckpointLogic = artifacts.require('./ERC20DividendCheckpoint.sol') const EtherDividendCheckpointFactory = artifacts.require('./EtherDividendCheckpointFactory.sol') const ERC20DividendCheckpointFactory = artifacts.require('./ERC20DividendCheckpointFactory.sol') +const LockUpTransferManagerFactory = artifacts.require('./LockUpTransferManagerFactory.sol') const BlacklistTransferManagerFactory = artifacts.require('./BlacklistTransferManagerFactory.sol') const VestingEscrowWalletFactory = artifacts.require('./VestingEscrowWalletFactory.sol'); const VestingEscrowWalletLogic = artifacts.require('./VestingEscrowWallet.sol'); @@ -186,6 +187,10 @@ module.exports = function (deployer, network, accounts) { // B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this // manager attach with the securityToken contract at the time of deployment) return deployer.deploy(GeneralTransferManagerFactory, PolyToken, 0, 0, 0, GeneralTransferManagerLogic.address, { from: PolymathAccount }); + }).then(() => { + // C) Deploy the LockUpTransferManagerFactory Contract (Factory used to generate the LockUpTransferManager contract and + // this manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(LockUpTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).then(() => { // C) Deploy the GeneralPermissionManagerFactory Contract (Factory used to generate the GeneralPermissionManager contract and // this manager attach with the securityToken contract at the time of deployment) @@ -197,7 +202,7 @@ module.exports = function (deployer, network, accounts) { }).then(() => { // D) Deploy the BlacklistTransferManagerFactory Contract (Factory used to generate the BlacklistTransferManager contract use // to to automate blacklist and restrict transfers) - return deployer.deploy(BlacklistTransferManagerFactory, PolyToken, 0, 0, 0, {from: PolymathAccount}); + return deployer.deploy(BlacklistTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).then(() => { // D) Deploy the PercentageTransferManagerFactory Contract (Factory used to generate the PercentageTransferManager contract use // to track the percentage of investment the investors could do for a particular security token) @@ -241,6 +246,10 @@ module.exports = function (deployer, network, accounts) { }).then(() => { // Update all addresses into the registry contract by calling the function updateFromregistry return moduleRegistry.updateFromRegistry({ from: PolymathAccount }); + }).then(() => { + // D) Register the LockUpTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the LockUpTransferManager contract. + return moduleRegistry.registerModule(LockUpTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // D) Register the PercentageTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the PercentageTransferManager contract. @@ -260,7 +269,7 @@ module.exports = function (deployer, network, accounts) { }).then(() => { // D) Register the BlacklistTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the GeneralTransferManager contract. - return moduleRegistry.registerModule(BlacklistTransferManagerFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(BlacklistTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // E) Register the GeneralPermissionManagerFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the GeneralPermissionManager contract. @@ -290,12 +299,17 @@ module.exports = function (deployer, network, accounts) { // G) Once the BlacklistTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(BlacklistTransferManagerFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(BlacklistTransferManagerFactory.address, true, { from: PolymathAccount }); }).then(() => { // G) Once the CountTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. return moduleRegistry.verifyModule(CountTransferManagerFactory.address, true, { from: PolymathAccount }); + }).then(() => { + // F) Once the LockUpTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(LockUpTransferManagerFactory.address, true, { from: PolymathAccount }); }).then(() => { // G) Once the PercentageTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. @@ -389,6 +403,7 @@ module.exports = function (deployer, network, accounts) { ERC20DividendCheckpointLogic: ${ERC20DividendCheckpointLogic.address} EtherDividendCheckpointFactory: ${EtherDividendCheckpointFactory.address} ERC20DividendCheckpointFactory: ${ERC20DividendCheckpointFactory.address} + LockUpTransferManagerFactory: ${LockUpTransferManagerFactory.address} BlacklistTransferManagerFactory: ${BlacklistTransferManagerFactory.address} VolumeRestrictionTMFactory: ${VolumeRestrictionTMFactory.address} VolumeRestrictionTMLogic: ${VolumeRestrictionTMLogic.address} diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index d204f7237..4335e52b3 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -26,7 +26,7 @@ const GeneralTransferManager = artifacts.require("./GeneralTransferManager.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"); @@ -301,11 +301,11 @@ export async function deployBlacklistTMAndVerified(accountPolymath, MRProxyInsta } 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_transfer_manager.js b/test/w_lockup_transfer_manager.js new file mode 100644 index 000000000..27924eb20 --- /dev/null +++ b/test/w_lockup_transfer_manager.js @@ -0,0 +1,995 @@ +import latestTime from './helpers/latestTime'; +import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall } from './helpers/encodeCall'; +import { setUpPolymathNetwork, deployLockupVolumeRTMAndVerified } from "./helpers/createInstances"; +import { catchRevert } from "./helpers/exceptions"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +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('LockUpTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let P_LockUpTransferManagerFactory; + let I_SecurityTokenRegistryProxy; + let P_LockUpTransferManager; + let I_GeneralTransferManagerFactory; + let I_LockUpTransferManagerFactory; + let I_GeneralPermissionManager; + let I_LockUpTransferManager; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_MRProxied; + let I_STFactory; + 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"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + 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"); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + + account_investor1 = accounts[7]; + account_investor2 = accounts[8]; + account_investor3 = accounts[9]; + + let instances = await setUpPolymathNetwork(account_polymath, token_owner); + + [ + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied + ] = instances; + + // STEP 4(c): Deploy the LockUpVolumeRestrictionTMFactory + [I_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 4(d): Deploy the LockUpVolumeRestrictionTMFactory + [P_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + + LockupVolumeRestrictionTransferManagerFactory: + ${I_LockUpTransferManagerFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.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(name, symbol, tokenDetails, false, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.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.getModulesByType(2))[0]; + 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 from non-divisible", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.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.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + 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( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('10', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }); + + 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_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + ) + }); + + 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_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, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + 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_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, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + 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_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, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + I_LockUpVolumeRestrictionTM_div = LockUpTransferManager.at(tx.logs[2].args._module); + }); + + it("Add a new token holder", async() => { + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor3, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Add the Investor in to the whitelist + // Mint some tokens + await I_SecurityToken.mint(account_investor3, web3.utils.toWei('10', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }); + + it("Should pause the tranfers at transferManager level", async() => { + let tx = await I_LockUpTransferManager.pause({from: token_owner}); + }); + + it("Should still be able to transfer between existing token holders up to limit", async() => { + // Transfer Some tokens between the investor + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('3', 'ether') + ); + }); + + it("Should unpause the tranfers at transferManager level", async() => { + 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_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + 0, + latestTime() + + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + "a_lockup", + { + from: token_owner + } + ) + ) + }); + + it("Should prevent the creation of a lockup with bad parameters where the releaseFrequencySeconds is zero", async() => { + // create a lockup + // this will generate an exception because the releaseFrequencySeconds is zero + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + 0, + "a_lockup", + { + from: token_owner + } + ) + ); + }); + + it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is zero", async() => { + // create a lockup + // this will generate an exception because the lockUpPeriodSeconds is zero + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime() + duration.seconds(1), + 0, + 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( + 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.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[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.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[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() => { + + 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 20 seconds each. + 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("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 }) + ); + }); + + 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("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 }) + ); + }); + + it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { + 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("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 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_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 }); + }); + + 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 }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }) + + it("Should allow transfer for the tokens that comes from secondary market + unlocked tokens", async() => { + + 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') + ); + }); + + 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 20 more seconds + 1 to get rid of same block time + 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(), + web3.utils.toWei('0', 'ether') + ); + }); + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + 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 + } + ) + ); + }) + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + 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 + } + ) + ); + }) + + 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, 50], + [20, 15], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ) + ); + }) + + 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, 50], + [20, 15, 10], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ) + ); + }) + + 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.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("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 }) + ); + + }); + + 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 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 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 remove multiple lockup --failed because of bad owner", async() => { + await catchRevert( + I_LockUpTransferManager.removeLockUpFromUserMulti( + [account_investor3, account_investor3], + ["c_lockup", "d_lockup"], + { + from: account_polymath + } + ) + ); + }); + + it("Should remove the multiple lockup -- failed because of invalid lockupname", async() => { + await catchRevert( + I_LockUpTransferManager.removeLockUpFromUserMulti( + [account_investor3, account_investor3], + ["c_lockup", "e_lockup"], + { + from: account_polymath + } + ) + ); + }) + + it("Should remove the multiple lockup", async() => { + await I_LockUpTransferManager.removeLockUpFromUserMulti( + [account_investor3, account_investor3], + ["d_lockup", "c_lockup"], + { + from: token_owner + } + ) + // do the free transaction now + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + }); + + 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.addNewLockUpToUser( + account_investor3, + web3.utils.toWei("9"), + latestTime() + duration.minutes(5), + 60, + 20, + "z_lockup", + { + from: token_owner + } + ); + + await catchRevert( + // edit the lockup + I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() + duration.seconds(1), + 60, + 20, + "z_lockup", + { + from: account_polymath + } + ) + ) + }) + + it("Modify the lockup when startTime is in past -- failed because startTime is in the past", async() => { + await catchRevert( + // edit the lockup + I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() - duration.seconds(50), + 60, + 20, + "z_lockup", + { + from: token_owner + } + ) + ) + }) + + it("Modify the lockup when startTime is in past -- failed because of invalid index", async() => { + await catchRevert( + // edit the lockup + I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() + duration.seconds(50), + 60, + 20, + "m_lockup", + { + from: token_owner + } + ) + ) + }) + + it("should successfully modify the lockup", async() => { + // edit the lockup + await I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() + duration.seconds(50), + 60, + 20, + "z_lockup", + { + from: token_owner + } + ); + }) + + it("Should prevent the transfer of tokens in an edited lockup", async() => { + + // balance here should be 12000000000000000000 (12e18 or 12 eth) + let balance = await I_SecurityToken.balanceOf(account_investor1) + + console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + + // create a lockup for their entire balance + // over 16 seconds total, with 4 periods of 4 seconds each. + await I_LockUpTransferManager.addNewLockUpToUser( + account_investor1, + balance, + latestTime() + duration.minutes(5), + 60, + 20, + "f_lockup", + { + from: token_owner + } + ); + + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) + ); + + 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( + 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); + + // edit the lockup + temp = latestTime() + duration.seconds(1); + await I_LockUpTransferManager.modifyLockUpType( + balance, + temp, + 60, + 20, + "f_lockup", + { + from: token_owner + } + ); + + // attempt a transfer + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) + ); + + // wait 20 seconds + await increaseTime(21); + + // transfer should succeed + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }); + + }); + + it("Modify the lockup during the lockup periods", async() => { + let balance = await I_SecurityToken.balanceOf(account_investor1) + 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( + balance, + latestTime() + duration.days(10), + 90, + 30, + "f_lockup", + { + from: token_owner + } + ); + }); + + 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 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() => { + let sig = await I_LockUpTransferManager.getInitFunction.call(); + 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(); + assert.equal(perm.length, 1); + assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ''), "ADMIN") + }); + + }); + + describe("LockUpTransferManager Transfer Manager Factory test cases", async() => { + + it("Should get the exact details of the factory", async() => { + 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, ''), + "LockUpTransferManager", + "Wrong Module added"); + assert.equal(await I_LockUpTransferManagerFactory.description.call(), + "Manage transfers using lock ups over time", + "Wrong Module added"); + assert.equal(await I_LockUpTransferManagerFactory.title.call(), + "LockUp Transfer Manager", + "Wrong Module added"); + 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_LockUpTransferManagerFactory.version.call(), "1.0.0"); + }); + + it("Should get the tags of the factory", async() => { + let tags = await I_LockUpTransferManagerFactory.getTags.call(); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "LockUp"); + }); + }); + +}); diff --git a/test/w_lockup_volume_restriction_transfer_manager.js b/test/w_lockup_volume_restriction_transfer_manager.js deleted file mode 100644 index 358e5b702..000000000 --- a/test/w_lockup_volume_restriction_transfer_manager.js +++ /dev/null @@ -1,808 +0,0 @@ -import latestTime from './helpers/latestTime'; -import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; -import { encodeProxyCall } from './helpers/encodeCall'; -import { setUpPolymathNetwork, deployLockupVolumeRTMAndVerified } from "./helpers/createInstances"; -import { catchRevert } from "./helpers/exceptions"; - -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const VolumeRestrictionTransferManager = artifacts.require('./LockupVolumeRestrictionTM'); -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 => { - - // Accounts Variable declaration - let account_polymath; - let account_issuer; - let token_owner; - let account_investor1; - let account_investor2; - let account_investor3; - let account_investor4; - - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - - let message = "Transaction Should Fail!"; - - // Contract Instance Declaration - let P_VolumeRestrictionTransferManagerFactory; - let I_SecurityTokenRegistryProxy; - let P_VolumeRestrictionTransferManager; - let I_GeneralTransferManagerFactory; - let I_VolumeRestrictionTransferManagerFactory; - let I_GeneralPermissionManager; - let I_VolumeRestrictionTransferManager; - let I_GeneralTransferManager; - let I_ModuleRegistryProxy; - let I_ModuleRegistry; - let I_FeatureRegistry; - let I_SecurityTokenRegistry; - let I_STRProxied; - let I_MRProxied; - let I_STFactory; - let I_SecurityToken; - let I_PolyToken; - let I_PolymathRegistry; - - // SecurityToken Details - const name = "Team"; - const symbol = "sap"; - const tokenDetails = "This is equity type of issuance"; - const decimals = 18; - const contact = "team@polymath.network"; - - // Module key - const delegateManagerKey = 1; - const transferManagerKey = 2; - const stoKey = 3; - - // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); - - before(async() => { - // Accounts setup - account_polymath = accounts[0]; - account_issuer = accounts[1]; - - token_owner = account_issuer; - - account_investor1 = accounts[7]; - account_investor2 = accounts[8]; - account_investor3 = accounts[9]; - - let instances = await setUpPolymathNetwork(account_polymath, token_owner); - - [ - I_PolymathRegistry, - I_PolyToken, - I_FeatureRegistry, - I_ModuleRegistry, - I_ModuleRegistryProxy, - I_MRProxied, - I_GeneralTransferManagerFactory, - I_STFactory, - I_SecurityTokenRegistry, - I_SecurityTokenRegistryProxy, - 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")); - - // Printing all the contract addresses - console.log(` - --------------------- Polymath Network Smart Contracts: --------------------- - PolymathRegistry: ${I_PolymathRegistry.address} - SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} - SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} - ModuleRegistry: ${I_ModuleRegistry.address} - ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} - FeatureRegistry: ${I_FeatureRegistry.address} - - STFactory: ${I_STFactory.address} - GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} - - LockupVolumeRestrictionTransferManagerFactory: - ${I_VolumeRestrictionTransferManagerFactory.address} - ----------------------------------------------------------------------------- - `); - }); - - describe("Generate the SecurityToken", async() => { - - it("Should register the ticker before the generation of the security token", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); - assert.equal(tx.logs[0].args._owner, token_owner); - assert.equal(tx.logs[0].args._ticker, symbol.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(name, symbol, tokenDetails, false, { from: token_owner }); - - // Verify the successful generation of the security token - assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); - - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.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.getModulesByType(2))[0]; - I_GeneralTransferManager = 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() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.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.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('2', 'ether') - ); - }); - - it("Should Buy some more tokens", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei('10', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), - web3.utils.toWei('10', 'ether') - ); - }); - - it("Should unsuccessfully attach the VolumeRestrictionTransferManager 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 }) - ) - }); - - it("Should successfully attach the VolumeRestrictionTransferManager 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"); - assert.equal( - web3.utils.toAscii(tx.logs[3].args._name) - .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "VolumeRestrictionTransferManagerFactory module was not added" - ); - P_VolumeRestrictionTransferManager = VolumeRestrictionTransferManager.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"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) - .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "VolumeRestrictionTransferManager module was not added" - ); - I_VolumeRestrictionTransferManager = VolumeRestrictionTransferManager.at(tx.logs[2].args._module); - }); - - it("Add a new token holder", async() => { - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Add the Investor in to the whitelist - // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei('10', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), - web3.utils.toWei('10', 'ether') - ); - }); - - it("Should pause the tranfers at transferManager level", async() => { - let tx = await I_VolumeRestrictionTransferManager.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 - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('3', 'ether') - ); - }); - - it("Should unpause the tranfers at transferManager level", async() => { - await I_VolumeRestrictionTransferManager.unpause({from: token_owner}); - }); - - it("Should prevent the creation of a lockup with bad parameters where the totalAmount is zero", async() => { - // create a lockup - // this will generate an exception because the totalAmount is zero - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, 0, { from: token_owner }) - ) - }); - - it("Should prevent the creation of a lockup with bad parameters where the releaseFrequencySeconds is zero", async() => { - // 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 }) - ); - }); - - it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is zero", async() => { - // 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 }) - ); - }); - - - 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_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, web3.utils.toWei('0.5', 'ether'), { from: token_owner }) - ); - }); - - 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_VolumeRestrictionTransferManager.addLockUp(account_investor2, 17, 4, 0, balance, { 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 }) - ); - }); - - 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_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, balance, { 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_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 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() => { - - 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 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 prevent the transfer of tokens in an edited lockup", async() => { - - - // balance here should be 12000000000000000000 (12e18 or 12 eth) - let balance = await I_SecurityToken.balanceOf(account_investor1) - - // 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 }); - - // let blockNumber = await web3.eth.getBlockNumber(); - // console.log('blockNumber',blockNumber) - let now = (await web3.eth.getBlock('latest')).timestamp - - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) - ); - - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 1) - - 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()); - - // edit the lockup - await I_VolumeRestrictionTransferManager.modifyLockUp(account_investor1, 0, 8, 4, 0, balance, { from: token_owner }); - - // attempt a transfer - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) - ); - - // 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); - await catchRevert( - I_VolumeRestrictionTransferManager.modifyLockUp(account_investor1, 8, 8, 4, 0, balance, { from: token_owner }) - ); - }) - - it("Should succesfully get the lockup - fail because array index out of bound", async() => { - await catchRevert( - I_VolumeRestrictionTransferManager.getLockUp(account_investor1, 9) - ); - }) - - 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() => { - await catchRevert( - I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 7, { 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 } - ); - - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }) - ); - - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', '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 revert if the parameters are bad when creating multiple lockups", async() => { - - 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 } - ) - ); - }); - - 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 - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - - }); - - 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. - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - - // wait 4 seconds for the lockup to begin - await increaseTime(duration.seconds(4)); - - // try another transfer. it should fail because the lockup has just begun - 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 }); - - - // try another transfer without waiting for another period to pass. it should fail - 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)); - - // try one final transfer. this should fail because the user has already withdrawn their entire balance - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - }); - - it("Should be possible to stack lockups", async() => { - // should be 17000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor1) - - // check and make sure that acct1 has no lockups so far - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount.toString(), 0) - - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 12, 4, 0, web3.utils.toWei('6', 'ether'), { from: token_owner }); - - // 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 }); - - // 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 }); - - // 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 }) - ); - - // 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 }); - - // try aother transfer. it should fail because it's locked up from both lockups again - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', '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 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 }); - - // 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("Should get configuration function signature", async() => { - let sig = await I_VolumeRestrictionTransferManager.getInitFunction.call(); - assert.equal(web3.utils.hexToNumber(sig), 0); - }); - - - it("Should get the permission", async() => { - let perm = await I_VolumeRestrictionTransferManager.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() => { - - 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()) - .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.description.call(), - "Manage transfers using lock ups over time", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.title.call(), - "Lockup Volume Restriction Transfer Manager", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.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"); - }); - - it("Should get the tags of the factory", async() => { - let tags = await I_VolumeRestrictionTransferManagerFactory.getTags.call(); - assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Volume"); - }); - }); - -}); 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'); diff --git a/test/z_general_permission_manager_fuzzer.js b/test/z_general_permission_manager_fuzzer.js index 83e98fa78..d80b36094 100644 --- a/test/z_general_permission_manager_fuzzer.js +++ b/test/z_general_permission_manager_fuzzer.js @@ -17,7 +17,6 @@ const SecurityToken = artifacts.require('./SecurityToken.sol'); const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); const CountTransferManager = artifacts.require("./CountTransferManager"); -const VolumeRestrictionTransferManager = artifacts.require('./LockupVolumeRestrictionTM'); const PercentageTransferManager = artifacts.require('./PercentageTransferManager'); const ManualApprovalTransferManager = artifacts.require('./ManualApprovalTransferManager'); From 3689174411295677029e73811e91d408340e9ffa Mon Sep 17 00:00:00 2001 From: satyam Date: Thu, 10 Jan 2019 01:42:20 +0530 Subject: [PATCH 14/17] add exempt list getter --- contracts/libraries/VolumeRestrictionLib.sol | 87 +++++++++++++ .../TransferManager/VolumeRestrictionTM.sol | 123 ++++++------------ .../VolumeRestrictionTMStorage.sol | 18 +-- migrations/2_deploy_contracts.js | 5 + test/y_volume_restriction_tm.js | 1 + 5 files changed, 141 insertions(+), 93 deletions(-) create mode 100644 contracts/libraries/VolumeRestrictionLib.sol diff --git a/contracts/libraries/VolumeRestrictionLib.sol b/contracts/libraries/VolumeRestrictionLib.sol new file mode 100644 index 000000000..fe3186369 --- /dev/null +++ b/contracts/libraries/VolumeRestrictionLib.sol @@ -0,0 +1,87 @@ +pragma solidity ^0.4.24; + +import "./BokkyPooBahsDateTimeLibrary.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../interfaces/ISecurityToken.sol"; + +library VolumeRestrictionLib { + + using SafeMath for uint256; + + enum TypeOfPeriod { MultipleDays, OneDay, Both } + + struct RestrictedHolder { + // 1 represent true & 0 for false + uint8 seen; + // Type of period will be enum index of TypeOfPeriod enum + uint8 typeOfPeriod; + // Index of the array where the holder address lives + uint128 index; + } + + struct RestrictedData { + mapping(address => RestrictedHolder) restrictedHolders; + address[] restrictedAddresses; + } + + function _checkLengthOfArray( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + uint256[] _restrictionTypes + ) + internal + pure + { + require( + _holders.length == _allowedTokens.length && + _allowedTokens.length == _startTimes.length && + _startTimes.length == _rollingPeriodInDays.length && + _rollingPeriodInDays.length == _endTimes.length && + _endTimes.length == _restrictionTypes.length, + "Length mismatch" + ); + } + + function _deleteHolderFromList(RestrictedData storage data, address _holder, uint8 _typeOfPeriod) public { + // Deleting the holder if holder's type of Period is `Both` type otherwise + // it will assign the given type `_typeOfPeriod` to the _holder typeOfPeriod + // `_typeOfPeriod` it always be contrary to the removing restriction + // if removing restriction is individual then typeOfPeriod is TypeOfPeriod.OneDay + // in uint8 its value is 1. if removing restriction is daily individual then typeOfPeriod + // is TypeOfPeriod.MultipleDays in uint8 its value is 0. + if (data.restrictedHolders[_holder].typeOfPeriod != uint8(TypeOfPeriod.Both)) { + uint128 index = data.restrictedHolders[_holder].index; + uint256 _len = data.restrictedAddresses.length; + if (index != _len) { + data.restrictedHolders[data.restrictedAddresses[_len - 1]].index = index; + data.restrictedAddresses[index - 1] = data.restrictedAddresses[_len - 1]; + } + delete data.restrictedHolders[_holder]; + data.restrictedAddresses.length--; + } else { + data.restrictedHolders[_holder].typeOfPeriod = _typeOfPeriod; + } + } + + function _addRestrictionData(RestrictedData storage data, address _holder, uint8 _callFrom, uint256 _endTime) public { + uint128 index = data.restrictedHolders[_holder].index; + if (data.restrictedHolders[_holder].seen == 0) { + data.restrictedAddresses.push(_holder); + index = uint128(data.restrictedAddresses.length); + } + uint8 _type = _getTypeOfPeriod(data.restrictedHolders[_holder].typeOfPeriod, _callFrom, _endTime); + data.restrictedHolders[_holder] = RestrictedHolder(uint8(1), _type, index); + } + + function _getTypeOfPeriod(uint8 _currentTypeOfPeriod, uint8 _callFrom, uint256 _endTime) internal view returns(uint8) { + if (_currentTypeOfPeriod != _callFrom && _endTime != uint256(0)) + return uint8(TypeOfPeriod.Both); + else + return _callFrom; + } + + +} \ No newline at end of file diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index 2bfa4edc2..c742e0b4a 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -137,7 +137,17 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { */ function changeExemptWalletList(address _wallet, bool _change) public withPerm(ADMIN) { require(_wallet != address(0)); - exemptList[_wallet] = _change; + require(exemptList[_wallet] != _change); + if (_change) { + exemptAddresses.push(_wallet); + exemptIndex[_wallet] = exemptAddresses.length; + exemptList[_wallet] = true; + } else { + exemptAddresses[exemptIndex[_wallet]] = exemptAddresses[exemptAddresses.length -1]; + exemptIndex[exemptAddresses[exemptIndex[_wallet]]] = exemptIndex[_wallet]; + delete exemptList[_wallet]; + exemptAddresses.length --; + } emit ChangedExemptWalletList(_wallet, _change); } @@ -203,7 +213,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _endTime, RestrictionType(_restrictionType) ); - _addRestrictionData(_holder, uint8(TypeOfPeriod.MultipleDays)); + VolumeRestrictionLib._addRestrictionData(holderData, _holder, uint8(TypeOfPeriod.MultipleDays), individualRestriction[_holder].endTime); emit AddIndividualRestriction( _holder, _allowedTokens, @@ -214,16 +224,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ); } - function _addRestrictionData(address _holder, uint8 _callFrom) internal { - uint128 index = restrictedHolders[_holder].index; - if (restrictedHolders[_holder].seen == 0) { - restrictedAddresses.push(_holder); - index = uint128(restrictedAddresses.length); - } - uint8 _type = _getTypeOfPeriod(restrictedHolders[_holder].typeOfPeriod, _callFrom, _holder); - restrictedHolders[_holder] = RestrictedHolder(uint8(1), _type, index); - } - /** * @notice Use to add the new individual daily restriction for all token holder * @param _holder Address of the token holder, whom restriction will be implied @@ -276,7 +276,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { _endTime, RestrictionType(_restrictionType) ); - _addRestrictionData(_holder, uint8(TypeOfPeriod.OneDay)); + VolumeRestrictionLib._addRestrictionData(holderData, _holder, uint8(TypeOfPeriod.OneDay), individualRestriction[_holder].endTime); emit AddIndividualDailyRestriction( _holder, _allowedTokens, @@ -306,7 +306,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { withPerm(ADMIN) { //NB - we duplicate _startTimes below to allow function reuse - _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); + VolumeRestrictionLib._checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { _addIndividualDailyRestriction( _holders[i], @@ -338,7 +338,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { public withPerm(ADMIN) { - _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + VolumeRestrictionLib._checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { _addIndividualRestriction( _holders[i], @@ -445,37 +445,16 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /// @notice Internal function to facilitate the removal of individual restriction function _removeIndividualRestriction(address _holder) internal { - require(_holder != address(0), "Invalid address"); + require(_holder != address(0)); require(individualRestriction[_holder].endTime != 0); individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); - _deleteHolderFromList(_holder, uint8(TypeOfPeriod.OneDay)); + VolumeRestrictionLib._deleteHolderFromList(holderData, _holder, uint8(TypeOfPeriod.OneDay)); userToBucket[_holder].lastTradedDayTime = 0; userToBucket[_holder].sumOfLastPeriod = 0; userToBucket[_holder].daysCovered = 0; emit IndividualRestrictionRemoved(_holder); } - function _deleteHolderFromList(address _holder, uint8 _typeOfPeriod) internal { - // Deleting the holder if holder's type of Period is `Both` type otherwise - // it will assign the given type `_typeOfPeriod` to the _holder typeOfPeriod - // `_typeOfPeriod` it always be contrary to the removing restriction - // if removing restriction is individual then typeOfPeriod is TypeOfPeriod.OneDay - // in uint8 its value is 1. if removing restriction is daily individual then typeOfPeriod - // is TypeOfPeriod.MultipleDays in uint8 its value is 0. - if (restrictedHolders[_holder].typeOfPeriod != uint8(TypeOfPeriod.Both)) { - uint128 index = restrictedHolders[_holder].index; - uint256 _len = restrictedAddresses.length; - if (index != _len) { - restrictedHolders[restrictedAddresses[_len - 1]].index = index; - restrictedAddresses[index - 1] = restrictedAddresses[_len - 1]; - } - delete restrictedHolders[_holder]; - restrictedAddresses.length--; - } else { - restrictedHolders[_holder].typeOfPeriod = _typeOfPeriod; - } - } - /** * @notice use to remove the individual restriction for a given address * @param _holders Array of address of the user @@ -496,10 +475,10 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { /// @notice Internal function to facilitate the removal of individual daily restriction function _removeIndividualDailyRestriction(address _holder) internal { - require(_holder != address(0), "Invalid address"); + require(_holder != address(0)); require(individualDailyRestriction[_holder].endTime != 0); individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); - _deleteHolderFromList(_holder, uint8(TypeOfPeriod.MultipleDays)); + VolumeRestrictionLib._deleteHolderFromList(holderData, _holder, uint8(TypeOfPeriod.MultipleDays)); userToBucket[_holder].dailyLastTradedDayTime = 0; emit IndividualDailyRestrictionRemoved(_holder); } @@ -677,7 +656,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { withPerm(ADMIN) { //NB - we duplicate _startTimes below to allow function reuse - _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); + VolumeRestrictionLib._checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { _modifyIndividualDailyRestriction( _holders[i], @@ -709,7 +688,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { public withPerm(ADMIN) { - _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + VolumeRestrictionLib._checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); for (uint256 i = 0; i < _holders.length; i++) { _modifyIndividualRestriction( _holders[i], @@ -745,7 +724,6 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { if (_startTime == 0) { startTime = now; } - /* require(startTime >= now, "Invalid startTime"); */ _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); defaultRestriction = VolumeRestriction( _allowedTokens, @@ -812,7 +790,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { */ function _defaultRestrictionCheck(address _from, uint256 _amount, bool _isTransfer) internal returns (Result) { // using the variable to avoid stack too deep error - BucketDetails memory bucketDetails = defaultUserToBucket[_from]; + BucketDetails storage bucketDetails = defaultUserToBucket[_from]; uint256 daysCovered = defaultRestriction.rollingPeriodInDays; uint256 fromTimestamp = 0; uint256 sumOfLastPeriod = 0; @@ -1087,30 +1065,11 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { "Invalid value" ); } - require(_endTime > _startTime, "Invalid times"); // Maximum limit for the rollingPeriod is 365 days require(_rollingPeriodDays >= 1 && _rollingPeriodDays <= 365, "Invalid rollingperiod"); - require(BokkyPooBahsDateTimeLibrary.diffDays(_startTime, _endTime) >= _rollingPeriodDays, "Invalid times"); - } - - function _checkLengthOfArray( - address[] _holders, - uint256[] _allowedTokens, - uint256[] _startTimes, - uint256[] _rollingPeriodInDays, - uint256[] _endTimes, - uint256[] _restrictionTypes - ) - internal - pure - { require( - _holders.length == _allowedTokens.length && - _allowedTokens.length == _startTimes.length && - _startTimes.length == _rollingPeriodInDays.length && - _rollingPeriodInDays.length == _endTimes.length && - _endTimes.length == _restrictionTypes.length, - "Length mismatch" + BokkyPooBahsDateTimeLibrary.diffDays(_startTime, _endTime) >= _rollingPeriodDays && _endTime > _startTime, + "Invalid times" ); } @@ -1164,11 +1123,11 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { return bytes4(0); } - function _getTypeOfPeriod(uint8 _currentTypeOfPeriod, uint8 _callFrom, address _holder) internal view returns(uint8) { - if (_currentTypeOfPeriod != _callFrom && individualRestriction[_holder].endTime != uint256(0)) - return uint8(TypeOfPeriod.Both); - else - return _callFrom; + /** + * @notice Use to return the list of exempted addresses + */ + function getExemptAddress() external view returns(address[]) { + return exemptAddresses; } /** @@ -1191,8 +1150,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { ) { uint256 counter = 0; uint256 i = 0; - for (i = 0; i < restrictedAddresses.length; i++) { - counter = counter + (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(2) ? 2 : 1); + for (i = 0; i < holderData.restrictedAddresses.length; i++) { + counter = counter + (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == uint8(2) ? 2 : 1); } allAddresses = new address[](counter); allowedTokens = new uint256[](counter); @@ -1201,19 +1160,19 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { endTime = new uint256[](counter); typeOfRestriction = new uint8[](counter); counter = 0; - for (i = 0; i < restrictedAddresses.length; i++) { - allAddresses[counter] = restrictedAddresses[i]; - if (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.MultipleDays)) { - _setValues(individualRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + for (i = 0; i < holderData.restrictedAddresses.length; i++) { + allAddresses[counter] = holderData.restrictedAddresses[i]; + if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.MultipleDays)) { + _setValues(individualRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); } - else if (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.OneDay)) { - _setValues(individualDailyRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + else if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.OneDay)) { + _setValues(individualDailyRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); } - else if (restrictedHolders[restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.Both)) { - _setValues(individualRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + else if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == uint8(TypeOfPeriod.Both)) { + _setValues(individualRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); counter = counter + 1; - allAddresses[counter] = restrictedAddresses[i]; - _setValues(individualDailyRestriction[restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + allAddresses[counter] = holderData.restrictedAddresses[i]; + _setValues(individualDailyRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); } counter ++; } diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol index ddc2b8e21..629f21be7 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol @@ -1,5 +1,7 @@ pragma solidity ^0.4.24; +import "../../libraries/VolumeRestrictionLib.sol"; + /** * @title Storage layout for VolumeRestrictionTM */ @@ -27,15 +29,6 @@ contract VolumeRestrictionTMStorage { uint256 dailyLastTradedDayTime; } - struct RestrictedHolder { - // 1 represent true & 0 for false - uint8 seen; - // Type of period will be enum index of TypeOfPeriod enum - uint8 typeOfPeriod; - // Index of the array where the holder address lives - uint128 index; - } - // Global restriction that applies to all token holders VolumeRestriction public defaultRestriction; // Daily global restriction that applies to all token holders (Total ST traded daily is restricted) @@ -52,7 +45,10 @@ contract VolumeRestrictionTMStorage { mapping(address => BucketDetails) internal defaultUserToBucket; // List of wallets that are exempted from all the restrictions applied by the this contract mapping(address => bool) public exemptList; - mapping(address => RestrictedHolder) internal restrictedHolders; - address[] public restrictedAddresses; + // Restricted data (refernce from the VolumeRestrictionLib library ) + VolumeRestrictionLib.RestrictedData holderData; + // Holde exempt index + mapping(address => uint256) exemptIndex; + address[] public exemptAddresses; } \ No newline at end of file diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index a89be3b90..9f0af4066 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -26,6 +26,7 @@ const STFactory = artifacts.require('./tokens/STFactory.sol') const DevPolyToken = artifacts.require('./helpers/PolyTokenFaucet.sol') const MockOracle = artifacts.require('./MockOracle.sol') const TokenLib = artifacts.require('./TokenLib.sol'); +const VolumeRestrictionLib = artifacts.require('./VolumeRestrictionLib.sol'); const SecurityToken = artifacts.require('./tokens/SecurityToken.sol') let BigNumber = require('bignumber.js'); @@ -140,7 +141,11 @@ module.exports = function (deployer, network, accounts) { // Deploy libraries return deployer.deploy(TokenLib, { from: PolymathAccount }); }).then(() => { + return deployer.deploy(VolumeRestrictionLib, { from: PolymathAccount }); + }).then(() => { + // Link libraries + deployer.link(VolumeRestrictionLib, VolumeRestrictionTMLogic); deployer.link(TokenLib, SecurityToken); deployer.link(TokenLib, STFactory); // A) Deploy the ModuleRegistry Contract (It contains the list of verified ModuleFactory) diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index 3610c13a3..db1a179b8 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -1514,6 +1514,7 @@ contract('VolumeRestrictionTransferManager', accounts => { it("Should add the token holder in the exemption list", async() => { await I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, {from: token_owner}); + console.log(await I_VolumeRestrictionTM.getExemptAddress.call()); let beforeBal = await I_SecurityToken.balanceOf.call(account_investor4); await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("3"), {from: account_investor4}); let afterBal = await I_SecurityToken.balanceOf.call(account_investor4); From c0700ea78a9ce75b25af982ae2400ca1a3bec9a0 Mon Sep 17 00:00:00 2001 From: satyam Date: Thu, 10 Jan 2019 12:00:41 +0530 Subject: [PATCH 15/17] minor fix --- contracts/libraries/VolumeRestrictionLib.sol | 2 +- .../TransferManager/VolumeRestrictionTM.sol | 4 +- test/y_volume_restriction_tm.js | 50 +++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/contracts/libraries/VolumeRestrictionLib.sol b/contracts/libraries/VolumeRestrictionLib.sol index fe3186369..e1e94a50b 100644 --- a/contracts/libraries/VolumeRestrictionLib.sol +++ b/contracts/libraries/VolumeRestrictionLib.sol @@ -76,7 +76,7 @@ library VolumeRestrictionLib { data.restrictedHolders[_holder] = RestrictedHolder(uint8(1), _type, index); } - function _getTypeOfPeriod(uint8 _currentTypeOfPeriod, uint8 _callFrom, uint256 _endTime) internal view returns(uint8) { + function _getTypeOfPeriod(uint8 _currentTypeOfPeriod, uint8 _callFrom, uint256 _endTime) internal pure returns(uint8) { if (_currentTypeOfPeriod != _callFrom && _endTime != uint256(0)) return uint8(TypeOfPeriod.Both); else diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index c742e0b4a..efeeff0d1 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -143,8 +143,8 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { exemptIndex[_wallet] = exemptAddresses.length; exemptList[_wallet] = true; } else { - exemptAddresses[exemptIndex[_wallet]] = exemptAddresses[exemptAddresses.length -1]; - exemptIndex[exemptAddresses[exemptIndex[_wallet]]] = exemptIndex[_wallet]; + exemptAddresses[exemptIndex[_wallet] - 1] = exemptAddresses[exemptAddresses.length -1]; + exemptIndex[exemptAddresses[exemptIndex[_wallet] -1 ]] = exemptIndex[_wallet]; delete exemptList[_wallet]; exemptAddresses.length --; } diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js index db1a179b8..335e76d1d 100644 --- a/test/y_volume_restriction_tm.js +++ b/test/y_volume_restriction_tm.js @@ -1521,6 +1521,56 @@ contract('VolumeRestrictionTransferManager', accounts => { let diff = beforeBal.minus(afterBal); assert.equal(web3.utils.fromWei((diff.toNumber()).toString()), 3); }); + + it("Should add multiple token holders to exemption list and check the getter value", async() => { + let holders = [account_investor1, account_investor3, account_investor2, account_delegate2]; + let change = [true, true, true, true]; + for (let i = 0; i < holders.length; i++) { + await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], {from: token_owner}); + } + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 5); + assert.equal(data[0], account_investor4); + assert.equal(data[1], account_investor1); + assert.equal(data[2], account_investor3); + assert.equal(data[3], account_investor2); + assert.equal(data[4], account_delegate2); + }); + + it("Should unexempt a particular address", async() => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, {from: token_owner}); + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 4); + assert.equal(data[0], account_investor4); + assert.equal(data[1], account_delegate2); + assert.equal(data[2], account_investor3); + assert.equal(data[3], account_investor2); + }); + + it("Should fail to unexempt the same address again", async() => { + await catchRevert( + I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, {from: token_owner}) + ); + }); + + it("Should delete the last element of the exemption list", async() => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor2, false, {from: token_owner}); + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 3); + assert.equal(data[0], account_investor4); + assert.equal(data[1], account_delegate2); + assert.equal(data[2], account_investor3); + }); + + it("Should delete multiple investor from the exemption list", async() => { + let holders = [account_delegate2, account_investor4, account_investor3]; + let change = [false, false, false]; + for (let i = 0; i < holders.length; i++) { + await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], {from: token_owner}); + } + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 0); + }); }); describe("Test for modify functions", async() => { From a2692fcedc9d2b16ff2dfb0b8e4344b7139b0638 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 10 Jan 2019 09:09:56 -0300 Subject: [PATCH 16/17] CLI - Added 'show exempted addresses' --- CLI/commands/transfer_manager.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 1ea70fa2b..39af92b78 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -1556,11 +1556,16 @@ async function volumeRestrictionTM() { let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedData().call(); console.log(`- Individual restrictions: ${addressesAndRestrictions.allAddresses.length}`); + let exemptedAddresses = await currentTransferManager.methods.getExemptAddress().call(); + console.log(`- Exempted addresses: ${exemptedAddresses.length}`); let options = []; - if (addressesAndRestrictions[0].length > 0) { + if (addressesAndRestrictions.allAddresses.length > 0) { options.push('Show restrictions'); } + if (exemptedAddresses.length > 0) { + options.push('Show exempted addresses'); + } options.push( 'Change exempt wallet', 'Change default restrictions', @@ -1583,6 +1588,9 @@ async function volumeRestrictionTM() { addressesAndRestrictions.endTime, ); break; + case 'Show exempted addresses': + showExemptedAddresses(exemptedAddresses); + break; case 'Change exempt wallet': await changeExemptWallet(); break; @@ -1620,6 +1628,11 @@ function showRestrictionTable(investorArray, amountArray, typeArray, rollingPeri console.log(table(dataTable)); } +function showExemptedAddresses(addresses) { + console.log("*********** Exepmpted addresses ***********"); + addresses.map(i => console.log(i)); +} + async function changeExemptWallet() { let options = [ 'Add exempt wallet', From 1b126c201a049d48006d170dcac5dbee01bacdd2 Mon Sep 17 00:00:00 2001 From: Adam Dossa Date: Thu, 10 Jan 2019 09:10:02 -0400 Subject: [PATCH 17/17] Small optimisation --- .../TransferManager/VolumeRestrictionTM.sol | 19 +++++++++---------- .../VolumeRestrictionTMStorage.sol | 10 +++++----- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol index efeeff0d1..5cf1e4ac6 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTM.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -112,7 +112,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { */ function verifyTransfer(address _from, address /*_to */, uint256 _amount, bytes /*_data*/, bool _isTransfer) public returns (Result) { // If `_from` is present in the exemptionList or it is `0x0` address then it will not follow the vol restriction - if (!paused && _from != address(0) && !exemptList[_from]) { + if (!paused && _from != address(0) && exemptIndex[_from] == 0) { // Function must only be called by the associated security token if _isTransfer == true require(msg.sender == securityToken || !_isTransfer); // Checking the individual restriction if the `_from` comes in the individual category @@ -137,15 +137,14 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { */ function changeExemptWalletList(address _wallet, bool _change) public withPerm(ADMIN) { require(_wallet != address(0)); - require(exemptList[_wallet] != _change); + require((exemptIndex[_wallet] == 0) == _change); if (_change) { exemptAddresses.push(_wallet); exemptIndex[_wallet] = exemptAddresses.length; - exemptList[_wallet] = true; } else { - exemptAddresses[exemptIndex[_wallet] - 1] = exemptAddresses[exemptAddresses.length -1]; - exemptIndex[exemptAddresses[exemptIndex[_wallet] -1 ]] = exemptIndex[_wallet]; - delete exemptList[_wallet]; + exemptAddresses[exemptIndex[_wallet] - 1] = exemptAddresses[exemptAddresses.length - 1]; + exemptIndex[exemptAddresses[exemptIndex[_wallet] - 1]] = exemptIndex[_wallet]; + delete exemptIndex[_wallet]; exemptAddresses.length --; } emit ChangedExemptWalletList(_wallet, _change); @@ -200,7 +199,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { individualRestriction[_holder].endTime < now, "Not Allowed" ); - require(_holder != address(0) && !exemptList[_holder], "Invalid address"); + require(_holder != address(0) && exemptIndex[_holder] == 0, "Invalid address"); _checkInputParams(_allowedTokens, _startTime, _rollingPeriodInDays, _endTime, _restrictionType, now); if (individualRestriction[_holder].endTime != 0) { @@ -1129,7 +1128,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { function getExemptAddress() external view returns(address[]) { return exemptAddresses; } - + /** * @notice Provide the restriction details of all the restricted addresses * @return address List of the restricted addresses @@ -1137,7 +1136,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { * @return uint256 List of the start time of the restriction corresponds to restricted address * @return uint256 List of the rolling period in days for a restriction corresponds to restricted address. * @return uint256 List of the end time of the restriction corresponds to restricted address. - * @return uint8 List of the type of restriction to validate the value of the `allowedTokens` + * @return uint8 List of the type of restriction to validate the value of the `allowedTokens` * of the restriction corresponds to restricted address */ function getRestrictedData() external view returns( @@ -1186,7 +1185,7 @@ contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { uint256[] memory endTime, uint8[] memory typeOfRestriction, uint256 index - ) + ) internal pure { diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol index 629f21be7..9eb776ff0 100644 --- a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol +++ b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol @@ -13,7 +13,7 @@ contract VolumeRestrictionTMStorage { struct VolumeRestriction { // If typeOfRestriction is `Percentage` then allowedTokens will be in - // the % (w.r.t to totalSupply) with a multiplier of 10**16 . else it + // the % (w.r.t to totalSupply) with a multiplier of 10**16 . else it // will be fixed amount of tokens uint256 allowedTokens; uint256 startTime; @@ -24,7 +24,7 @@ contract VolumeRestrictionTMStorage { struct BucketDetails { uint256 lastTradedDayTime; - uint256 sumOfLastPeriod; // It is the sum of transacted amount within the last rollingPeriodDays + uint256 sumOfLastPeriod; // It is the sum of transacted amount within the last rollingPeriodDays uint256 daysCovered; // No of days covered till (from the startTime of VolumeRestriction) uint256 dailyLastTradedDayTime; } @@ -44,11 +44,11 @@ contract VolumeRestrictionTMStorage { // Storing the information related to default restriction mapping(address => BucketDetails) internal defaultUserToBucket; // List of wallets that are exempted from all the restrictions applied by the this contract - mapping(address => bool) public exemptList; + /* mapping(address => bool) public exemptList; */ // Restricted data (refernce from the VolumeRestrictionLib library ) VolumeRestrictionLib.RestrictedData holderData; // Holde exempt index mapping(address => uint256) exemptIndex; address[] public exemptAddresses; - -} \ No newline at end of file + +}