diff --git a/.travis.yml b/.travis.yml index b676c5840..b2fb9bdc4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ jobs: include: - stage: test before_script: truffle version - script: npm run test + script: travis_wait 120 sleep infinity & npm run test notifications: slack: secure: W4FZSabLrzF74f317hutolEHnlq2GBlQxU6b85L5XymrjgLEhlgE16c5Qz7Emoyt6le6PXL+sfG2ujJc3XYys/6hppgrHSAasuJnKCdQNpmMZ9BNyMs6WGkmB3enIf3K/FLXb26AQdwpQdIXuOeJUTf879u+YoiZV0eZH8d3+fsIOyovq9N6X5pKOpDM9iT8gGB4t7fie7xf51s+iUaHxyO9G7jDginZ4rBXHcU7mxCub9z+Z1H8+kCTnPWaF+KKVEXx4Z0nI3+urboD7E4OIP02LwrThQls2CppA3X0EoesTcdvj/HLErY/JvsXIFiFEEHZzB1Wi+k2TiOeLcYwEuHIVij+HPxxlJNX/j8uy01Uk8s4rd+0EhvfdKHJqUKqxH4YN2npcKfHEss7bU3y7dUinXQfYShW5ZewHdvc7pnnxBTfhvmdi64HdNrXAPq+s1rhciH7MmnU+tsm4lhrpr+FBuHzUMA9fOCr7b0SQytZEgWpiUls88gdbh3yG8TjyZxmZJGx09cwEP0q7VoH0UwFh7mIu5XmYdd5tWUhavTiO7YV8cUPn7MvwMsTltB3YBpF/fB26L7ka8zBhCsjm9prW6SVYU/dyO3m91VeZtO/zJFHRDA6Q58JGVW2rgzO39z193qC1EGRXqTie96VwAAtNg8+hRb+bI/CWDVzSPc= \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1c20409..9e1bbb0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,13 @@ All notable changes to this project will be documented in this file. * Changed the version of `ERC20DividendCheckpointFactory` & `EtherDividendCheckpointFactory` from `1.0.0` to `2.1.0`. * Applied proxy pattern to Dividends modules +## Experimental modules +* Remove the `SingleTradeVolumeRestrictionTMFactory.sol` and its corresponding module `SingleTradeVolumeRestrictionTM.sol`. + +## Added +* Add new module called `VolumeRestrictionTM.sol` under the TransferManager modules list. It will be used to restrict the token +volume traded in a given rolling period. + ## Changed * `getAllModulesAndPermsFromTypes()` does not take securityToken address as a parameter anymore. diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index 329a789ff..e394b2eac 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -10,6 +10,7 @@ let generalTransferManagerABI; let manualApprovalTransferManagerABI; let countTransferManagerABI; let percentageTransferManagerABI; +let volumeRestrictionTMABI; let generalPermissionManagerABI; let polyTokenABI; let cappedSTOFactoryABI; @@ -36,6 +37,7 @@ 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; + volumeRestrictionTMABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/VolumeRestrictionTM.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; @@ -90,6 +92,9 @@ module.exports = { percentageTransferManager: function () { return percentageTransferManagerABI; }, + volumeRestrictionTM: function () { + return volumeRestrictionTMABI; + }, generalPermissionManager: function () { return generalPermissionManagerABI; }, diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 5e97cd7a9..00396b3ea 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -15,6 +15,14 @@ const PERCENTAGE_WHITELIST_DATA_CSV = `${__dirname}/../data/Transfer/PercentageT const ADD_MANUAL_APPROVAL_DATA_CSV = `${__dirname}/../data/Transfer/MATM/add_manualapproval_data.csv`; const MODIFY_MANUAL_APPROVAL_DATA_CSV = `${__dirname}/../data/Transfer/MATM/modify_manualapproval_data.csv`; const REVOKE_MANUAL_APPROVAL_DATA_CSV = `${__dirname}/../data/Transfer/MATM/revoke_manualapproval_data.csv`; +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 RESTRICTION_TYPES = ['Fixed', 'Percentage']; const MATM_MENU_ADD = 'Add new manual approval'; const MATM_MENU_MANAGE = 'Manage existing approvals'; @@ -226,23 +234,10 @@ async function configExistingModules(tmModules) { currentTransferManager.setProvider(web3.currentProvider); await percentageTransferManager(); break; - case 'SingleTradeVolumeRestrictionTM': - //currentTransferManager = new web3.eth.Contract(abis.singleTradeVolumeRestrictionTM(), tmModules[index].address); - //currentTransferManager.setProvider(web3.currentProvider); - //await singleTradeVolumeRestrictionTM(); - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); - break; - case 'LookupVolumeRestrictionTM': - //await lookupVolumeRestrictionTM(); - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); + case 'VolumeRestrictionTM': + currentTransferManager = new web3.eth.Contract(abis.volumeRestrictionTM(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await volumeRestrictionTM(); break; } } @@ -275,56 +270,6 @@ async function addTransferManagerModule() { let configurePercentageTM = abis.percentageTransferManager().find(o => o.name === 'configure' && o.type === 'function'); bytes = web3.eth.abi.encodeFunctionCall(configurePercentageTM, [maxHolderPercentage, allowPercentagePrimaryIssuance]); break; - case 'SingleTradeVolumeRestrictionTM': - /* - let isTransferLimitInPercentage = !!readlineSync.keyInSelect(['In tokens', 'In percentage'], 'How do you want to set the transfer limit? ', {cancel: false}); - let globalTransferLimitInPercentageOrToken; - if (isTransferLimitInPercentage) { - globalTransferLimitInPercentageOrToken = toWeiPercentage(readlineSync.question('Enter the percentage for default limit: ', { - limit: function(input) { - return (parseInt(input) > 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be greater than 0 and less than 100" - })); - } else { - globalTransferLimitInPercentageOrToken = web3.utils.toWei(readlineSync.question('Enter the amount of tokens for default limit: ', { - limit: function(input) { - return parseInt(input) > 0; - }, - limitMessage: "Must be greater than 0" - })); - } - let allowPrimaryIssuance = readlineSync.keyInYNStrict(`Do you want to allow all primary issuance transfers? `); - bytes = web3.eth.abi.encodeFunctionCall( { - name: 'configure', - type: 'function', - inputs: [ - { - type: 'bool', - name: '_isTransferLimitInPercentage' - },{ - type: 'uint256', - name: '_globalTransferLimitInPercentageOrToken' - },{ - type: 'bool', - name: '_isTransferLimitInPercentage' - } - ] - }, [isTransferLimitInPercentage, globalTransferLimitInPercentageOrToken, allowPrimaryIssuance]); - */ - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); - break; - case 'LookupVolumeRestrictionTM': - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); - break; } let selectedTMFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.TRANSFER, options[index]); let addModuleAction = securityToken.methods.addModule(selectedTMFactoryAddress, bytes, 0, 0); @@ -427,10 +372,10 @@ async function generalTransferManager() { limitMessage: "Must be a valid address" }); let now = Math.floor(Date.now() / 1000); - let fromTime = readlineSync.questionInt(`Enter the time(Unix Epoch time) when the sale lockup period ends and the investor can freely sell his tokens(now = ${now}): `, { defaultInput: now }); - let toTime = readlineSync.questionInt(`Enter the time(Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others(now = ${now}): `, { defaultInput: now }); + let fromTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) when the sale lockup period ends and the investor can freely sell his tokens (now = ${now}): `, { defaultInput: now }); + let toTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others (now = ${now}): `, { defaultInput: now }); let oneHourFromNow = Math.floor(Date.now() / 1000 + 3600); - let expiryTime = readlineSync.questionInt(`Enter the time till investors KYC will be validated(after that investor need to do re - KYC) (1 hour from now = ${oneHourFromNow}): `, { defaultInput: oneHourFromNow }); + let expiryTime = readlineSync.questionInt(`Enter the time till investors KYC will be validated (after that investor need to do re - KYC) (1 hour from now = ${oneHourFromNow}): `, { defaultInput: oneHourFromNow }); let canBuyFromSTO = readlineSync.keyInYNStrict('Is the investor a restricted investor?'); let modifyWhitelistAction = currentTransferManager.methods.modifyWhitelist(investor, fromTime, toTime, expiryTime, canBuyFromSTO); let modifyWhitelistReceipt = await common.sendTransaction(modifyWhitelistAction); @@ -1108,186 +1053,633 @@ async function percentageTransferManager() { } } -async function singleTradeVolumeRestrictionTM() { - console.log(chalk.blue(`Single Trade Volume Restriction Transfer Manager at ${currentTransferManager.options.address} `)); - console.log(); +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; + + 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 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')} `); + } - // Show current data - let displayIsInPercentage = await currentTransferManager.methods.isTransferLimitInPercentage().call(); - let displayGlobalTransferLimit; - if (displayIsInPercentage) { - displayGlobalTransferLimit = fromWeiPercentage(await currentTransferManager.methods.globalTransferLimitInPercentage().call()); + let options = [ + '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 'Change exempt wallet': + await changeExemptWallet(); + break; + case 'Change default restrictions': + await changeDefaultRestrictions(hasDefaultDailyRestriction, hasDefaultRestriction); + break; + case 'Change individual restrictions': + await changeIndividualRestrictions(); + break; + case 'Explore account': + await exploreAccount(); + break; + case 'Operate with multiple restrictions': + await operateWithMultipleRestrictions(); + break; + case 'RETURN': + return; + } + + await volumeRestrictionTM(); +} + +async function changeExemptWallet() { + let options = [ + 'Add exempt wallet', + 'Remove exempt wallet' + ]; + + let change; + 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 exempt wallet': + change = true; + break; + case 'Remove exempt wallet': + change = false; + break; + case 'RETURN': + return; + } + + let wallet = readlineSync.question('Enter the wallet to change: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let changeExemptWalletAction = currentTransferManager.methods.changeExemptWalletList(wallet, change); + let changeExemptWalletReceipt = await common.sendTransaction(changeExemptWalletAction); + let changeExemptWalletEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeExemptWalletReceipt.logs, 'ChangedExemptWalletList'); + console.log(chalk.green(`${changeExemptWalletEvent._wallet} has been ${changeExemptWalletEvent._change ? `added to` : `removed from`} exempt wallets successfully!`)); +} + +async function changeDefaultRestrictions(hasDefaultDailyRestriction, hasDefaultRestriction) { + let options = []; + if (!hasDefaultDailyRestriction) { + options.push('Add default daily restriction'); } else { - displayGlobalTransferLimit = web3.utils.fromWei(await currentTransferManager.methods.globalTransferLimitInTokens().call()); + options.push('Modify default daily restriction', 'Remove default daily restriction'); } - let displayAllowPrimaryIssuance = await currentTransferManager.methods.allowPrimaryIssuance().call(); - console.log(`- Limit type: ${displayIsInPercentage ? `Percentage` : `Tokens`} `); - console.log(`- Default transfer limit: ${displayGlobalTransferLimit} ${displayIsInPercentage ? `%` : `${tokenSymbol}`} `); - console.log(`- Allow primary issuance: ${displayAllowPrimaryIssuance ? `YES` : `NO`} `); - // ------------------ + if (!hasDefaultRestriction) { + options.push('Add default restriction'); + } else { + options.push('Modify default restriction', 'Remove default 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 + ); + 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 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 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 modifyDefaultRestrictionReceipt = await common.sendTransaction(modifyDefaultRestrictionAction); + let modifyDefaultRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyDefaultRestrictionReceipt.logs, 'ModifyDefaultRestriction'); + console.log(chalk.green(`Default 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!`)); + break; + } +} + +async function changeIndividualRestrictions() { + let holder = readlineSync.question('Enter the address of the token holder, whom restriction will be implied: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + + 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; + + console.log(`*** Current individual restrictions for ${holder} ***`, '\n'); + + 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)}%`}`); + console.log(` Start time: ${moment.unix(currentDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + 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')} `); + } let options = []; - if (displayAllowPrimaryIssuance) { - options.push('Disallow primary issuance'); + if (!hasDailyRestriction) { + options.push('Add individual daily restriction'); } else { - options.push('Allow primary issuance'); + options.push('Modify individual daily restriction', 'Remove individual daily restriction'); } - options.push('Add exempted wallet', 'Remove exempted wallet'); - if (displayIsInPercentage) { - options.push('Change transfer limit to tokens', 'Change default percentage limit', - 'Set percentage transfer limit per account', 'Remove percentage transfer limit per account'); + + if (!hasRestriction) { + options.push('Add individual restriction'); } else { - options.push('Change transfer limit to percentage', 'Change default tokens limit', - 'Set tokens transfer limit per account', 'Remove tokens transfer limit per account'); + options.push('Modify individual restriction', 'Remove individual restriction'); } - let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Return' }); - let optionSelected = options[index]; - console.log('Selected:', index != -1 ? optionSelected : 'Return', '\n'); + 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 'Allow primary issuance': - case 'Disallow primary issuance': - let disallowPrimaryIssuanceAction = currentTransferManager.methods.setAllowPrimaryIssuance(!displayAllowPrimaryIssuance); - await common.sendTransaction(disallowPrimaryIssuanceAction); + case 'Add individual daily restriction': + let dailyRestrictoToAdd = inputRestrictionData(true); + let addDailyRestrictionAction = currentTransferManager.methods.addIndividualDailyRestriction( + holder, + dailyRestrictoToAdd.allowedTokens, + dailyRestrictoToAdd.startTime, + dailyRestrictoToAdd.endTime, + dailyRestrictoToAdd.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 'Add exempted wallet': - let walletToExempt = readlineSync.question('Enter the wallet to exempt: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let addExemptWalletAction = currentTransferManager.methods.addExemptWallet(walletToExempt); - let addExemptWalletReceipt = await common.sendTransaction(addExemptWalletAction); - let addExemptWalletEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addExemptWalletReceipt.logs, 'ExemptWalletAdded'); - console.log(chalk.green(`${addExemptWalletEvent._wallet} has been exempted sucessfully!`)); + case 'Modify individual daily restriction': + let dailyRestrictoToModify = inputRestrictionData(true); + let modifyDailyRestrictionAction = currentTransferManager.methods.modifyIndividualDailyRestriction( + holder, + dailyRestrictoToModify.allowedTokens, + dailyRestrictoToModify.startTime, + dailyRestrictoToModify.endTime, + dailyRestrictoToModify.restrictionType + ); + let modifyDailyRestrictionReceipt = await common.sendTransaction(modifyDailyRestrictionAction); + let modifyDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyDailyRestrictionReceipt.logs, 'ModifyIndividualDailyRestriction'); + console.log(chalk.green(`Daily restriction for ${modifyDailyRestrictionEvent._holder} has been modified successfully!`)); break; - case 'Remove exempted wallet': - let exemptedWallet = readlineSync.question('Enter the wallet to remove from exempt: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let removeExemptWalletAction = currentTransferManager.methods.removeExemptWallet(exemptedWallet); - let removeExemptWalletReceipt = await common.sendTransaction(removeExemptWalletAction); - let removeExemptWalletEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeExemptWalletReceipt.logs, 'ExemptWalletRemoved'); - console.log(chalk.green(`${removeExemptWalletEvent._wallet} has been removed from exempt wallets sucessfully!`)); + case 'Remove individual daily restriction': + let removeDailyRestrictionAction = currentTransferManager.methods.removeIndividualDailyRestriction(holder); + let removeDailyRestrictionReceipt = await common.sendTransaction(removeDailyRestrictionAction); + let removeDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeDailyRestrictionReceipt.logs, 'IndividualDailyRestrictionRemoved'); + console.log(chalk.green(`Daily restriction for ${removeDailyRestrictionEvent._holder} has been removed successfully!`)); break; - case 'Change transfer limit to tokens': - let newDefaultLimitInTokens = web3.utils.toWei(readlineSync.question('Enter the amount of tokens for default limit: ', { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: "Must be greater than zero" - })); - let changeTransferLimitToTokensAction = currentTransferManager.methods.changeTransferLimitToTokens(newDefaultLimitInTokens); - let changeTransferLimitToTokensReceipt = await common.sendTransaction(changeTransferLimitToTokensAction); - let changeTransferLimitToTokensEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeTransferLimitToTokensReceipt.logs, 'GlobalTransferLimitInTokensSet'); - console.log(chalk.green(`Transfer limit has been set to tokens sucessfully!`)); - console.log(chalk.green(`The default transfer limit is ${web3.utils.fromWei(changeTransferLimitToTokensEvent._amount)} ${tokenSymbol} `)); + case 'Add individual restriction': + let restrictoToAdd = inputRestrictionData(false); + let addRestrictionAction = currentTransferManager.methods.addIndividualRestriction( + holder, + restrictoToAdd.allowedTokens, + restrictoToAdd.startTime, + restrictoToAdd.rollingPeriodInDays, + restrictoToAdd.endTime, + restrictoToAdd.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!`)); break; - case 'Change transfer limit to percentage': - let newDefaultLimitInPercentage = toWeiPercentage(readlineSync.question('Enter the percentage for default limit: ', { - limit: function (input) { - return (parseInt(input) > 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be greater than 0 and less than 100" - })); - let changeTransferLimitToPercentageAction = currentTransferManager.methods.changeTransferLimitToPercentage(newDefaultLimitInPercentage); - let changeTransferLimitToPercentageReceipt = await common.sendTransaction(changeTransferLimitToPercentageAction); - let changeTransferLimitToPercentageEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeTransferLimitToPercentageReceipt.logs, 'GlobalTransferLimitInPercentageSet'); - console.log(chalk.green(`Transfer limit has been set to tokens sucessfully!`)); - console.log(chalk.green(`The default transfer limit is ${fromWeiPercentage(changeTransferLimitToPercentageEvent._percentage)} % `)); + case 'Modify individual restriction': + let restrictoToModify = inputRestrictionData(false); + let modifyRestrictionAction = currentTransferManager.methods.modifyIndividualRestriction( + holder, + restrictoToModify.allowedTokens, + restrictoToModify.startTime, + restrictoToModify.rollingPeriodInDays, + restrictoToModify.endTime, + restrictoToModify.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!`)); break; - case 'Change default percentage limit': - let defaultLimitInPercentage = toWeiPercentage(readlineSync.question('Enter the percentage for default limit: ', { - limit: function (input) { - return (parseInt(input) > 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be greater than 0 and less than 100" - })); - let changeGlobalLimitInPercentageAction = currentTransferManager.methods.changeGlobalLimitInPercentage(defaultLimitInPercentage); - let changeGlobalLimitInPercentageReceipt = await common.sendTransaction(changeGlobalLimitInPercentageAction); - let changeGlobalLimitInPercentageEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeGlobalLimitInPercentageReceipt.logs, 'GlobalTransferLimitInPercentageSet'); - console.log(chalk.green(`The default transfer limit is ${fromWeiPercentage(changeGlobalLimitInPercentageEvent._percentage)} % `)); + 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!`)); break; - case 'Change default tokens limit': - let defaultLimitInTokens = web3.utils.toWei(readlineSync.question('Enter the amount of tokens for default limit: ', { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: "Must be greater than zero" - })); - let changeGlobalLimitInTokensAction = currentTransferManager.methods.changeGlobalLimitInTokens(defaultLimitInTokens); - let changeGlobalLimitInTokensReceipt = await common.sendTransaction(changeGlobalLimitInTokensAction); - let changeGlobalLimitInTokensEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeGlobalLimitInTokensReceipt.logs, 'GlobalTransferLimitInTokensSet'); - console.log(chalk.green(`The default transfer limit is ${web3.utils.fromWei(changeGlobalLimitInTokensEvent._amount)} ${tokenSymbol} `)); + case 'RETURN': + return; + } +} + +async function exploreAccount() { + let account = readlineSync.question('Enter the account to explore: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + + let appliyngdDailyRestriction = null; + let applyingOtherRestriction = 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 otherRestriction = await currentTransferManager.methods.individualRestriction(account).call(); + if (parseInt(otherRestriction.endTime) !== 0) { + applyingOtherRestriction = otherRestriction; + } + + hasIndividualRestrictions = applyingOtherRestriction || appliyngdDailyRestriction; + + if (!hasIndividualRestrictions) { + let defaultDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); + if (parseInt(defaultDailyRestriction.endTime) !== 0) { + appliyngdDailyRestriction = defaultDailyRestriction; + } + let defaultOtherRestriction = await currentTransferManager.methods.defaultRestriction().call(); + if (parseInt(defaultOtherRestriction.endTime) === 0) { + applyingOtherRestriction = defaultOtherRestriction; + } + } + } + + 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(`- 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')} `); + } + + if (applyingOtherRestriction || appliyngdDailyRestriction) { + let bucketDetails; + if (hasIndividualRestrictions) { + bucketDetails = await currentTransferManager.methods.getIndividualBucketDetailsToUser(account).call(); + } else { + bucketDetails = await currentTransferManager.methods.getDefaultBucketDetailsToUser(account).call(); + } + let now = Math.floor(Date.now() / 1000) - gbl.constants.DURATION.days(1); + let tradedByUserLastDay = await currentTransferManager.methods.getTotalTradedByUser(account, now).call(); + console.log(); + console.log(`Last trade: ${bucketDetails[0]}`); + console.log(`Last daily trade: ${bucketDetails[3]}`); + console.log(`Days since rolling period started: ${bucketDetails[2]}`); + console.log(`Transacted amount since rolling period started: ${web3.utils.fromWei(bucketDetails[1])}`); + console.log(`Transacted amount within last 24 hours: ${web3.utils.fromWei(tradedByUserLastDay)}`); + console.log(); + } +} + +async function operateWithMultipleRestrictions() { + let options = [ + 'Add multiple individual daily restrictions', + 'Modify multiple individual daily restrictions', + 'Remove multiple individual daily restrictions', + 'Add multiple individual restrictions', + 'Modify multiple individual restrictions', + 'Remove multiple individual 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 'Add multiple individual daily restrictions': + await addDailyRestrictionsInBatch(); break; - case 'Set percentage transfer limit per account': - let percentageAccount = readlineSync.question('Enter the wallet: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let accountLimitInPercentage = toWeiPercentage(readlineSync.question(`Enter the transfer limit for ${percentageAccount} in percentage: `, { - limit: function (input) { - return (parseInt(input) > 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be greater than 0 and less than 100" - })); - let setTransferLimitInPercentageAction = currentTransferManager.methods.setTransferLimitInPercentage(percentageAccount, accountLimitInPercentage); - let setTransferLimitInPercentageReceipt = await common.sendTransaction(setTransferLimitInPercentageAction); - let setTransferLimitInPercentageEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, setTransferLimitInPercentageReceipt.logs, 'TransferLimitInPercentageSet'); - console.log(chalk.green(`The transfer limit for ${setTransferLimitInPercentageEvent._wallet} is ${fromWeiPercentage(setTransferLimitInPercentageEvent._percentage)} % `)); + case 'Modify multiple individual daily restrictions': + await modifyDailyRestrictionsInBatch(); break; - case 'Set tokens transfer limit per account': - let tokensAccount = readlineSync.question('Enter the wallet: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let accountLimitInTokens = web3.utils.toWei(readlineSync.question(`Enter the transfer limit for ${tokensAccount} in amount of tokens: `, { - limit: function (input) { - return parseInt(input) > 0; - }, - limitMessage: "Must be greater than zero" - })); - let setTransferLimitInTokensAction = currentTransferManager.methods.setTransferLimitInTokens(tokensAccount, accountLimitInTokens); - let setTransferLimitInTokensReceipt = await common.sendTransaction(setTransferLimitInTokensAction); - let setTransferLimitInTokensEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, setTransferLimitInTokensReceipt.logs, 'TransferLimitInTokensSet'); - console.log(chalk.green(`The transfer limit for ${setTransferLimitInTokensEvent._wallet} is ${web3.utils.fromWei(setTransferLimitInTokensEvent._amount)} ${tokenSymbol} `)); + case 'Remove multiple individual daily restrictions': + await removeDailyRestrictionsInBatch(); break; - case 'Remove percentage transfer limit per account': - let percentageAccountToRemove = readlineSync.question('Enter the wallet to remove: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let removeTransferLimitInPercentageAction = currentTransferManager.methods.removeTransferLimitInPercentage(percentageAccountToRemove); - let removeTransferLimitInPercentageReceipt = await common.sendTransaction(removeTransferLimitInPercentageAction); - let removeTransferLimitInPercentageEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeTransferLimitInPercentageReceipt.logs, 'TransferLimitInPercentageRemoved'); - console.log(chalk.green(`The transfer limit for ${removeTransferLimitInPercentageEvent._wallet} is the default limit`)); + case 'Add multiple individual restrictions': + await addRestrictionsInBatch(); break; - case 'Remove tokens transfer limit per account': - let tokensAccountToRemove = readlineSync.question('Enter the wallet to remove: ', { - limit: function (input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let removeTransferLimitInTokensAction = currentTransferManager.methods.removeTransferLimitInTokens(tokensAccountToRemove); - let removeTransferLimitInTokensReceipt = await common.sendTransaction(removeTransferLimitInTokensAction); - let removeTransferLimitInTokensEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeTransferLimitInTokensReceipt.logs, 'TransferLimitInTokensRemoved'); - console.log(chalk.green(`The transfer limit for ${removeTransferLimitInTokensEvent._wallet} is the default limit`)); + case 'Modify multiple individual restrictions': + await modifyRestrictionsInBatch(); + break; + case 'Remove multiple individual restrictions': + await removeRestrictionsInBatch(); break; } } +async function addDailyRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_DAILY_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: ADD_DAILY_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) { + 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]) && + !isNaN(row[1]) && + moment.unix(row[2]).isValid() && + moment.unix(row[3]).isValid() && + typeof row[4] === 'string' && RESTRICTION_TYPES.includes(row[4])); + 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 [holderArray, allowanceArray, startTimeArray, endTimeArray, restrictionTypeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add daily 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.addIndividualDailyRestrictionMulti(holderArray[batch], allowanceArray[batch], startTimeArray[batch], endTimeArray[batch], restrictionTypeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple daily 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 modifyDailyRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_DAILY_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: MODIFY_DAILY_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) { + 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]) && + !isNaN(row[1]) && + moment.unix(row[2]).isValid() && + moment.unix(row[3]).isValid() && + typeof row[4] === 'string' && RESTRICTION_TYPES.includes(row[4])); + 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 [holderArray, allowanceArray, startTimeArray, endTimeArray, restrictionTypeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify daily 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.modifyIndividualDailyRestrictionMulti(holderArray[batch], allowanceArray[batch], startTimeArray[batch], endTimeArray[batch], restrictionTypeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple daily 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 removeDailyRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_DAILY_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: REMOVE_DAILY_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) { + 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])); + 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 [holderArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove daily restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + let action = currentTransferManager.methods.removeIndividualDailyRestrictionMulti(holderArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove multiple daily 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 addRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: ADD_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) { + 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]) && + !isNaN(row[1]) && + moment.unix(row[2]).isValid() && + (!isNaN(row[3]) && (parseFloat(row[3]) % 1 === 0)) && + moment.unix(row[4]).isValid() && + typeof row[5] === 'string' && RESTRICTION_TYPES.includes(row[5])); + 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 [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'); + 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(`${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 + }); + 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]) && + !isNaN(row[1]) && + moment.unix(row[2]).isValid() && + (!isNaN(row[3]) && (parseFloat(row[3]) % 1 === 0)) && + moment.unix(row[4]).isValid() && + typeof row[5] === 'string' && RESTRICTION_TYPES.includes(row[5])); + 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 [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'); + 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(`${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 + }); + 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])); + 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 [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'); + 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(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +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()); + } else { + restriction.allowedTokens = toWeiPercentage(readlineSync.questionInt(`Enter the maximum percentage of total supply allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); + } + if (isDaily) { + restriction.rollingPeriodInDays = 1; + } else { + restriction.rollingPeriodInDays = readlineSync.questionInt(`Enter the rolling period in days (10 days): `, { defaultInput: 10 }); + } + 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}): `, { + limit: function (input) { + return input > restriction.startTime + gbl.constants.DURATION.days(restriction.rollingPeriodInDays); + }, + limitMessage: 'Must be greater than startTime + rolling period', + defaultInput: oneMonthFromNow + }); + return restriction; +} + /* // Copied from tests function signData(tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo, pk) { @@ -1417,7 +1809,7 @@ async function selectToken() { async function logTotalInvestors() { let investorsCount = await securityToken.methods.getInvestorCount().call(); - console.log(chalk.yellow(`Total investors at the moment: ${investorsCount}`)); + console.log(chalk.yellow(`Total investors at the moment: ${investorsCount} `)); } async function logBalance(from, totalSupply) { diff --git a/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv b/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv new file mode 100644 index 000000000..782c1107d --- /dev/null +++ b/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv @@ -0,0 +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 diff --git a/CLI/data/Transfer/VRTM/add_restriction_data.csv b/CLI/data/Transfer/VRTM/add_restriction_data.csv new file mode 100644 index 000000000..072151a64 --- /dev/null +++ b/CLI/data/Transfer/VRTM/add_restriction_data.csv @@ -0,0 +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 diff --git a/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv new file mode 100644 index 000000000..194e9b1d5 --- /dev/null +++ b/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv @@ -0,0 +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" diff --git a/CLI/data/Transfer/VRTM/modify_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_restriction_data.csv new file mode 100644 index 000000000..588e39211 --- /dev/null +++ b/CLI/data/Transfer/VRTM/modify_restriction_data.csv @@ -0,0 +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 diff --git a/CLI/data/Transfer/VRTM/remove_daily_restriction_data.csv b/CLI/data/Transfer/VRTM/remove_daily_restriction_data.csv new file mode 100644 index 000000000..a7fef30c4 --- /dev/null +++ b/CLI/data/Transfer/VRTM/remove_daily_restriction_data.csv @@ -0,0 +1,6 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1 +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1 +0xac297053173b02b02a737d47f7b4a718e5b170ef +0x49fc0b78238dab644698a90fa351b4c749e123d2 +0x10223927009b8add0960359dd90d1449415b7ca9 +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399 diff --git a/CLI/data/Transfer/VRTM/remove_restriction_data.csv b/CLI/data/Transfer/VRTM/remove_restriction_data.csv new file mode 100644 index 000000000..d927ee57b --- /dev/null +++ b/CLI/data/Transfer/VRTM/remove_restriction_data.csv @@ -0,0 +1,2 @@ +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98 +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3 diff --git a/CLI/polymath-cli.js b/CLI/polymath-cli.js index 94df66e0a..66b8badf8 100644 --- a/CLI/polymath-cli.js +++ b/CLI/polymath-cli.js @@ -12,9 +12,9 @@ const transfer_manager = require('./commands/transfer_manager'); const contract_manager = require('./commands/contract_manager'); const strMigrator = require('./commands/strMigrator'); const permission_manager = require('./commands/permission_manager'); -const time = require('./commands/helpers/time') +const time = require('./commands/helpers/time'); const gbl = require('./commands/common/global'); -const program = require('commander'); +const program = require('commander'); const moment = require('moment'); const yaml = require('js-yaml'); const fs = require('fs'); @@ -162,24 +162,23 @@ program await permission_manager.executeApp(); }); -program - .command('time_travel') - .alias('tt') - .option('-p, --period ', 'Period of time in seconds to increase') - .option('-d, --toDate ', 'Human readable date ("MM/DD/YY [HH:mm:ss]") to travel to') - .option('-e, --toEpochTime ', 'Unix Epoch time to travel to') - .description('Increases time on EVM according to given value.') - .action(async function (cmd) { - await gbl.initialize(program.remoteNode); - if (cmd.period) { - await time.increaseTimeByDuration(parseInt(cmd.period)); - } else if (cmd.toDate) { - await time.increaseTimeToDate(cmd.toDate); - } else if (cmd.toEpochTime) { - await time.increaseTimeToEpochDate(cmd.toEpochTime); - } + program + .command('time_travel') + .alias('tt') + .option('-p, --period ', 'Period of time in seconds to increase') + .option('-d, --toDate ', 'Human readable date ("MM/DD/YY [HH:mm:ss]") to travel to') + .option('-e, --toEpochTime ', 'Unix Epoch time to travel to') + .description('Increases time on EVM according to given value.') + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + if (cmd.period) { + await time.increaseTimeByDuration(parseInt(cmd.period)); + } else if (cmd.toDate) { + await time.increaseTimeToDate(cmd.toDate); + } else if (cmd.toEpochTime) { + await time.increaseTimeToEpochDate(cmd.toEpochTime); + } }); - program.parse(process.argv); if (typeof program.commands.length == 0) { diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol new file mode 100644 index 000000000..c452b4663 --- /dev/null +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -0,0 +1,1174 @@ +pragma solidity ^0.4.24; + +import "./ITransferManager.sol"; +import "./VolumeRestrictionTMStorage.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../libraries/BokkyPooBahsDateTimeLibrary.sol"; + +contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { + + using SafeMath for uint256; + + // permission definition + bytes32 public constant ADMIN = "ADMIN"; + + // Emit when the token holder is added/removed from the exemption list + event ChangedExemptWalletList(address indexed _wallet, bool _change); + // Emit when the new individual restriction is added corresponds to new token holders + event AddIndividualRestriction( + address indexed _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when the new daily (Individual) restriction is added + event AddIndividualDailyRestriction( + address indexed _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when the individual restriction is modified for a given address + event ModifyIndividualRestriction( + address indexed _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when individual daily restriction get modified + event ModifyIndividualDailyRestriction( + address indexed _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when the new global restriction is added + event AddDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when the new daily (Default) restriction is added + event AddDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when default restriction get modified + event ModifyDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when daily default restriction get modified + event ModifyDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _typeOfRestriction + ); + // Emit when the individual restriction gets removed + event IndividualRestrictionRemoved(address indexed _holder); + // Emit when individual daily restriction removed + event IndividualDailyRestrictionRemoved(address indexed _holder); + // Emit when the default restriction gets removed + event DefaultRestrictionRemoved(); + // Emit when the daily default restriction gets removed + event DefaultDailyRestrictionRemoved(); + + /** + * @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/transferFrom transaction and prevent tranaction + * whose volume of tokens will voilate the maximum volume transfer restriction + * @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) { + // 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]) { + // 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) + || (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 + */ + function changeExemptWalletList(address _wallet, bool _change) public withPerm(ADMIN) { + require(_wallet != address(0), "Invalid address"); + exemptList[_wallet] = _change; + emit ChangedExemptWalletList(_wallet, _change); + } + + /** + * @notice Use to add the new individual restriction for a given token holder + * @param _holder Address of the token holder, whom restriction will be implied + * @param _allowedTokens Amount of tokens allowed to be trade for a given address. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _rollingPeriodInDays Rolling period in days (Minimum value should be 1 day) + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function addIndividualRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + _addIndividualRestriction( + _holder, + _allowedTokens, + _startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /// @notice Internal function to facilitate the addition of individual restriction + function _addIndividualRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _restrictionType + ) + internal + { + + uint256 startTime = _startTime; + if (_startTime == 0) { + startTime = now; + } + require( + individualRestriction[_holder].endTime < now, + "Already present" + ); + require(_holder != address(0) && !exemptList[_holder], "Invalid address"); + require(startTime >= now, "Invalid startTime"); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); + + if (individualRestriction[_holder].endTime != 0) { + _removeIndividualRestriction(_holder); + } + individualRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + RestrictionType(_restrictionType) + ); + emit AddIndividualRestriction( + _holder, + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /** + * @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 + * @param _allowedTokens Amount of tokens allowed to be traded for all token holder. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function addIndividualDailyRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + _addIndividualDailyRestriction( + _holder, + _allowedTokens, + _startTime, + _endTime, + _restrictionType + ); + } + + /// @notice Internal function to facilitate the addition of individual daily restriction + function _addIndividualDailyRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + uint256 _restrictionType + ) + internal + { + + uint256 startTime = _startTime; + if (_startTime == 0) { + startTime = now; + } + require( + individualDailyRestriction[_holder].endTime < now, + "Not Allowed" + ); + require(startTime >= now, "Invalid startTime"); + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType); + individualDailyRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + RestrictionType(_restrictionType) + ); + emit AddIndividualDailyRestriction( + _holder, + _allowedTokens, + startTime, + 1, + _endTime, + _restrictionType + ); + } + + /** + * @notice Use to add the new individual daily restriction for multiple token holders + * @param _holders Array of address of the token holders, whom restriction will be implied + * @param _allowedTokens Array of amount of tokens allowed to be trade for a given address. + * @param _startTimes Array of unix timestamps at which restrictions get into effect + * @param _endTimes Array of unix timestamps at which restriction effects will gets end. + * @param _restrictionTypes Array of restriction types value will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function addIndividualDailyRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _endTimes, + uint256[] _restrictionTypes + ) + public + withPerm(ADMIN) + { + 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] + ); + } + } + + /** + * @notice Use to add the new individual restriction for multiple token holders + * @param _holders Array of address of the token holders, whom restriction will be implied + * @param _allowedTokens Array of amount of tokens allowed to be trade for a given address. + * @param _startTimes Array of unix timestamps at which restrictions get into effect + * @param _rollingPeriodInDays Array of rolling period in days (Minimum value should be 1 day) + * @param _endTimes Array of unix timestamps at which restriction effects will gets end. + * @param _restrictionTypes Array of restriction types value will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function addIndividualRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + uint256[] _restrictionTypes + ) + public + withPerm(ADMIN) + { + _checkLengthOfArray(_allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + require(_holders.length == _allowedTokens.length, "Length mismatch"); + for (uint256 i = 0; i < _holders.length; i++) { + _addIndividualRestriction( + _holders[i], + _allowedTokens[i], + _startTimes[i], + _rollingPeriodInDays[i], + _endTimes[i], + _restrictionTypes[i] + ); + } + } + + /** + * @notice Use to add the new default restriction for all token holder + * @param _allowedTokens Amount of tokens allowed to be traded for all token holder. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _rollingPeriodInDays Rolling period in days (Minimum value should be 1 day) + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function addDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + uint256 startTime = _startTime; + if (_startTime == 0) { + startTime = now; + } + require( + defaultRestriction.endTime < now, + "Not allowed" + ); + require(startTime >= now, "Invalid startTime"); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); + defaultRestriction = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + RestrictionType(_restrictionType) + ); + emit AddDefaultRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /** + * @notice Use to add the new default daily restriction for all token holder + * @param _allowedTokens Amount of tokens allowed to be traded for all token holder. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function addDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + uint256 startTime = _startTime; + if (_startTime == 0) { + startTime = now; + } + require( + defaultDailyRestriction.endTime < now, + "Not Allowed" + ); + require(startTime >= now, "Invalid startTime"); + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType); + defaultDailyRestriction = VolumeRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + RestrictionType(_restrictionType) + ); + emit AddDefaultDailyRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + _restrictionType + ); + } + + /** + * @notice use to remove the individual restriction for a given address + * @param _holder Address of the user + */ + function removeIndividualRestriction(address _holder) external withPerm(ADMIN) { + _removeIndividualRestriction(_holder); + } + + /// @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"); + individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + userToBucket[_holder].lastTradedDayTime = 0; + userToBucket[_holder].sumOfLastPeriod = 0; + userToBucket[_holder].daysCovered = 0; + emit IndividualRestrictionRemoved(_holder); + } + + /** + * @notice use to remove the individual restriction for a given address + * @param _holders Array of address of the user + */ + function removeIndividualRestrictionMulti(address[] _holders) external withPerm(ADMIN) { + for (uint256 i = 0; i < _holders.length; i++) { + _removeIndividualRestriction(_holders[i]); + } + } + + /** + * @notice use to remove the individual daily restriction for a given address + * @param _holder Address of the user + */ + function removeIndividualDailyRestriction(address _holder) external withPerm(ADMIN) { + _removeIndividualDailyRestriction(_holder); + } + + /// @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"); + individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + userToBucket[_holder].dailyLastTradedDayTime = 0; + emit IndividualDailyRestrictionRemoved(_holder); + } + + /** + * @notice use to remove the individual daily restriction for a given address + * @param _holders Array of address of the user + */ + function removeIndividualDailyRestrictionMulti(address[] _holders) external withPerm(ADMIN) { + for (uint256 i = 0; i < _holders.length; i++) { + _removeIndividualDailyRestriction(_holders[i]); + } + } + + /** + * @notice Use to remove the default restriction + */ + function removeDefaultRestriction() public withPerm(ADMIN) { + require(defaultRestriction.endTime != 0); + defaultRestriction = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + emit DefaultRestrictionRemoved(); + } + + /** + * @notice Use to remove the daily default restriction + */ + function removeDefaultDailyRestriction() external withPerm(ADMIN) { + 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 + * @param _holder Address of the token holder, whom restriction will be implied + * @param _allowedTokens Amount of tokens allowed to be trade for a given address. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _rollingPeriodInDays Rolling period in days (Minimum value should be 1 day) + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function modifyIndividualRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + _modifyIndividualRestriction( + _holder, + _allowedTokens, + _startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /// @notice Internal function to facilitate the modification of individual restriction + function _modifyIndividualRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _restrictionType + ) + internal + { + uint256 startTime = _startTime; + if (_startTime == 0) { + startTime = now; + } + require(individualRestriction[_holder].startTime > now, "Not allowed"); + require(startTime >= now, "Invalid startTime"); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType); + individualRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + RestrictionType(_restrictionType) + ); + emit ModifyIndividualRestriction( + _holder, + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /** + * @notice Use to modify the existing individual daily restriction for a given token holder + * @dev Changing of startTime will affect the 24 hrs span. i.e if in earlier restriction days start with + * morning and end on midnight while after the change day may start with afternoon and end with other day afternoon + * @param _holder Address of the token holder, whom restriction will be implied + * @param _allowedTokens Amount of tokens allowed to be trade for a given address. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function modifyIndividualDailyRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + _modifyIndividualDailyRestriction( + _holder, + _allowedTokens, + _startTime, + _endTime, + _restrictionType + ); + } + + /// @notice Internal function to facilitate the modification of individual daily restriction + function _modifyIndividualDailyRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + uint256 _restrictionType + ) + internal + { + 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 + // 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, + 1, + _endTime, + RestrictionType(_restrictionType) + ); + emit ModifyIndividualDailyRestriction( + _holder, + _allowedTokens, + startTime, + 1, + _endTime, + _restrictionType + ); + } + + /** + * @notice Use to modify the existing individual daily restriction for multiple token holders + * @param _holders Array of address of the token holders, whom restriction will be implied + * @param _allowedTokens Array of amount of tokens allowed to be trade for a given address. + * @param _startTimes Array of unix timestamps at which restrictions get into effect + * @param _endTimes Array of unix timestamps at which restriction effects will gets end. + * @param _restrictionTypes Array of restriction types value will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function modifyIndividualDailyRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _endTimes, + uint256[] _restrictionTypes + ) + public + withPerm(ADMIN) + { + require( + _allowedTokens.length == _startTimes.length && + _startTimes.length == _holders.length && + _holders.length == _endTimes.length && + _endTimes.length == _restrictionTypes.length, + "Array length mismatch" + ); + require(_holders.length == _allowedTokens.length, "Length mismatch"); + for (uint256 i = 0; i < _holders.length; i++) { + _modifyIndividualDailyRestriction( + _holders[i], + _allowedTokens[i], + _startTimes[i], + _endTimes[i], + _restrictionTypes[i] + ); + } + } + + /** + * @notice Use to modify the existing individual restriction for multiple token holders + * @param _holders Array of address of the token holders, whom restriction will be implied + * @param _allowedTokens Array of amount of tokens allowed to be trade for a given address. + * @param _startTimes Array of unix timestamps at which restrictions get into effect + * @param _rollingPeriodInDays Array of rolling period in days (Minimum value should be 1 day) + * @param _endTimes Array of unix timestamps at which restriction effects will gets end. + * @param _restrictionTypes Array of restriction types value will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function modifyIndividualRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + uint256[] _restrictionTypes + ) + public + withPerm(ADMIN) + { + _checkLengthOfArray(_allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + require(_holders.length == _allowedTokens.length, "Length mismatch"); + for (uint256 i = 0; i < _holders.length; i++) { + _modifyIndividualRestriction( + _holders[i], + _allowedTokens[i], + _startTimes[i], + _rollingPeriodInDays[i], + _endTimes[i], + _restrictionTypes[i] + ); + } + } + + /** + * @notice Use to modify the global restriction for all token holder + * @param _allowedTokens Amount of tokens allowed to be traded for all token holder. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _rollingPeriodInDays Rolling period in days (Minimum value should be 1 day) + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function modifyDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + uint256 _restrictionType + ) + external + withPerm(ADMIN) + { + 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); + defaultRestriction = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + RestrictionType(_restrictionType) + ); + emit ModifyDefaultRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /** + * @notice Use to modify the daily default restriction for all token holder + * @dev Changing of startTime will affect the 24 hrs span. i.e if in earlier restriction days start with + * morning and end on midnight while after the change day may start with afternoon and end with other day afternoon. + * @param _allowedTokens Amount of tokens allowed to be traded for all token holder. + * @param _startTime Unix timestamp at which restriction get into effect + * @param _endTime Unix timestamp at which restriction effects will gets end. + * @param _restrictionType It will be 0 or 1 (i.e 0 for fixed while 1 for Percentage) + */ + function modifyDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + uint256 _restrictionType + ) + 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 + // 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, + 1, + _endTime, + RestrictionType(_restrictionType) + ); + emit ModifyDefaultDailyRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + _restrictionType + ); + } + + /** + * @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) { + // using the variable to avoid stack too deep error + BucketDetails memory bucketDetails = defaultUserToBucket[_from]; + uint256 daysCovered = defaultRestriction.rollingPeriodInDays; + uint256 fromTimestamp = 0; + uint256 sumOfLastPeriod = 0; + uint256 dailyTime = 0; + bool allowedDefault = true; + bool allowedDaily; + if (defaultRestriction.endTime >= now && defaultRestriction.startTime <= now) { + if (bucketDetails.lastTradedDayTime < defaultRestriction.startTime) { + // It will execute when the txn is performed first time after the addition of individual restriction + fromTimestamp = defaultRestriction.startTime; + } else { + // 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( + fromTimestamp, + BokkyPooBahsDateTimeLibrary.diffDays(fromTimestamp, now), + _from, + daysCovered, + bucketDetails + ); + // validation of the transaction amount + if (!_checkValidAmountToTransact(sumOfLastPeriod, _amount, defaultRestriction)) { + allowedDefault = false; + } + } + (allowedDaily, dailyTime) = _dailyTxCheck(_from, _amount, bucketDetails.dailyLastTradedDayTime, defaultDailyRestriction); + + if (_isTransfer) { + _updateStorage( + _from, + _amount, + fromTimestamp, + sumOfLastPeriod, + daysCovered, + dailyTime, + defaultDailyRestriction.endTime, + true + ); + } + return ((allowedDaily && allowedDefault) == true ? Result.NA : Result.INVALID); + } + + /** + * @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) { + // using the variable to avoid stack too deep error + BucketDetails memory bucketDetails = userToBucket[_from]; + VolumeRestriction memory dailyRestriction = individualDailyRestriction[_from]; + VolumeRestriction memory restriction = individualRestriction[_from]; + uint256 daysCovered = individualRestriction[_from].rollingPeriodInDays; + uint256 fromTimestamp = 0; + uint256 sumOfLastPeriod = 0; + uint256 dailyTime = 0; + bool allowedIndividual = true; + bool allowedDaily; + if (restriction.endTime >= now && restriction.startTime <= now) { + if (bucketDetails.lastTradedDayTime < restriction.startTime) { + // It will execute when the txn is performed first time after the addition of individual restriction + fromTimestamp = restriction.startTime; + } else { + // 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( + fromTimestamp, + BokkyPooBahsDateTimeLibrary.diffDays(fromTimestamp, now), + _from, + daysCovered, + bucketDetails + ); + // validation of the transaction amount + if (!_checkValidAmountToTransact(sumOfLastPeriod, _amount, restriction)) { + allowedIndividual = false; + } + } + (allowedDaily, dailyTime) = _dailyTxCheck(_from, _amount, bucketDetails.dailyLastTradedDayTime, dailyRestriction); + + if (_isTransfer) { + _updateStorage( + _from, + _amount, + fromTimestamp, + sumOfLastPeriod, + daysCovered, + dailyTime, + dailyRestriction.endTime, + false + ); + } + + return ((allowedDaily && allowedIndividual) ? Result.NA : Result.INVALID); + } + + function _dailyTxCheck( + address from, + 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) { + uint256 txSumOfDay = 0; + if (dailyLastTradedDayTime == 0 || dailyLastTradedDayTime < restriction.startTime) + // This if condition will be executed when the individual daily restriction executed first time + dailyLastTradedDayTime = restriction.startTime.add(BokkyPooBahsDateTimeLibrary.diffDays(restriction.startTime, now).mul(1 days)); + else if (now.sub(dailyLastTradedDayTime) >= 1 days) + dailyLastTradedDayTime = dailyLastTradedDayTime.add(BokkyPooBahsDateTimeLibrary.diffDays(dailyLastTradedDayTime, now).mul(1 days)); + // Assgining total sum traded on dailyLastTradedDayTime timestamp + txSumOfDay = bucket[from][dailyLastTradedDayTime]; + return (_checkValidAmountToTransact(txSumOfDay, amount, restriction), dailyLastTradedDayTime); + } + return (true, dailyLastTradedDayTime); + } + + /// Internal function for the bucket check + function _bucketCheck( + uint256 _fromTime, + uint256 _diffDays, + address _from, + uint256 _rollingPeriodInDays, + BucketDetails memory _bucketDetails + ) + internal + view + returns (uint256, uint256, uint256) + { + uint256 counter = _bucketDetails.daysCovered; + uint256 sumOfLastPeriod = _bucketDetails.sumOfLastPeriod; + uint256 i = 0; + if (_diffDays >= _rollingPeriodInDays) { + // If the difference of days is greater than the rollingPeriod then sumOfLastPeriod will always be zero + sumOfLastPeriod = 0; + counter = counter.add(_diffDays); + } else { + for (i = 0; i < _diffDays; i++) { + counter++; + // This condition is to check whether the first rolling period is covered or not + // if not then it continues and adding 0 value into sumOfLastPeriod without subtracting + // the earlier value at that index + 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. + // Loop starts from the first day covered in sumOfLastPeriod upto the day that is covered by rolling period. + sumOfLastPeriod = + sumOfLastPeriod.sub( + bucket[_from][_bucketDetails.lastTradedDayTime.sub( + ( + _bucketDetails.daysCovered.sub( + counter.sub( + _rollingPeriodInDays + ) + ) + ).mul(1 days) + )] + ); + } + // Adding the last amount that is transacted on the `_fromTime` not actually doing it but left written to understand + // the alogrithm + //_bucketDetails.sumOfLastPeriod = _bucketDetails.sumOfLastPeriod.add(uint256(0)); + } + } + // 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 .... + // where T1,T2,T3 are timestamps having 24 hrs difference + _fromTime = _fromTime.add(_diffDays.mul(1 days)); + return (sumOfLastPeriod, _fromTime, counter); + } + + function _checkValidAmountToTransact( + 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; + } else { + _allowedAmount = _restriction.allowedTokens; + } + // Validation on the amount to transact + return (_allowedAmount >= _sumOfLastPeriod.add(_amountToTransact)); + } + + function _updateStorage( + address _from, + uint256 _amount, + uint256 _lastTradedDayTime, + uint256 _sumOfLastPeriod, + uint256 _daysCovered, + uint256 _dailyLastTradedDayTime, + uint256 _endTime, + bool isDefault + ) + internal + { + + if (isDefault){ + BucketDetails storage defaultUserToBucketDetails = defaultUserToBucket[_from]; + _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, _endTime, defaultUserToBucketDetails); + } + else { + BucketDetails storage userToBucketDetails = userToBucket[_from]; + _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, _endTime, userToBucketDetails); + } + } + + function _updateStorageActual( + address _from, + uint256 _amount, + uint256 _lastTradedDayTime, + uint256 _sumOfLastPeriod, + uint256 _daysCovered, + uint256 _dailyLastTradedDayTime, + uint256 _endTime, + BucketDetails storage details + ) + internal + { + // Cheap storage technique + if (details.lastTradedDayTime != _lastTradedDayTime) { + // Assigning the latest transaction timestamp of the day + details.lastTradedDayTime = _lastTradedDayTime; + } + if (details.dailyLastTradedDayTime != _dailyLastTradedDayTime) { + // Assigning the latest transaction timestamp of the day + details.dailyLastTradedDayTime = _dailyLastTradedDayTime; + } + if (details.daysCovered != _daysCovered) { + details.daysCovered = _daysCovered; + } + if (_amount != 0) { + if (_lastTradedDayTime !=0) { + details.sumOfLastPeriod = _sumOfLastPeriod.add(_amount); + // Increasing the total amount of the day by `_amount` + bucket[_from][_lastTradedDayTime] = bucket[_from][_lastTradedDayTime].add(_amount); + } + if ((_dailyLastTradedDayTime != _lastTradedDayTime) && _dailyLastTradedDayTime != 0 && now <= _endTime) { + // 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 _endTime, + uint256 _restrictionType + ) + internal + pure + { + require(_restrictionType == 0 || _restrictionType == 1, "Invalid type"); + if (_restrictionType == uint256(RestrictionType.Fixed)) { + require(_allowedTokens > 0, "Invalid value"); + } else { + require( + _allowedTokens > 0 && _allowedTokens <= 100 * 10 ** 16, + "Percentage is not within (0,100]" + ); + } + 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"); + } + + function _checkLengthOfArray( + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + uint256[] _restrictionTypes + ) + internal + pure + { + require( + _allowedTokens.length == _startTimes.length && + _startTimes.length == _rollingPeriodInDays.length && + _rollingPeriodInDays.length == _endTimes.length && + _endTimes.length == _restrictionTypes.length, + "Array length mismatch" + ); + } + + /** + * @notice Use to get the bucket details for a given address + * @param _user Address of the token holder for whom the bucket details has queried + * @return uint256 lastTradedDayTime + * @return uint256 sumOfLastPeriod + * @return uint256 days covered + * @return uint256 24h lastTradedDayTime + */ + function getIndividualBucketDetailsToUser(address _user) external view returns(uint256, uint256, uint256, uint256) { + return( + userToBucket[_user].lastTradedDayTime, + userToBucket[_user].sumOfLastPeriod, + userToBucket[_user].daysCovered, + userToBucket[_user].dailyLastTradedDayTime + ); + } + + /** + * @notice Use to get the bucket details for a given address + * @param _user Address of the token holder for whom the bucket details has queried + * @return uint256 lastTradedDayTime + * @return uint256 sumOfLastPeriod + * @return uint256 days covered + * @return uint256 24h lastTradedDayTime + */ + function getDefaultBucketDetailsToUser(address _user) external view returns(uint256, uint256, uint256, uint256) { + return( + defaultUserToBucket[_user].lastTradedDayTime, + defaultUserToBucket[_user].sumOfLastPeriod, + defaultUserToBucket[_user].daysCovered, + defaultUserToBucket[_user].dailyLastTradedDayTime + ); + } + + /** + * @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 + */ + function getTotalTradedByUser(address _user, uint256 _at) external view returns(uint256) { + return bucket[_user][_at]; + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public view 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; + } + +} \ No newline at end of file diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol b/contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol new file mode 100644 index 000000000..7d2e27cc0 --- /dev/null +++ b/contracts/modules/TransferManager/VolumeRestrictionTMFactory.sol @@ -0,0 +1,75 @@ +pragma solidity ^0.4.24; + +import "../../proxy/VolumeRestrictionTMProxy.sol"; +import "../ModuleFactory.sol"; + +/** + * @title Factory for deploying VolumeRestrictionTM module + */ +contract VolumeRestrictionTMFactory is ModuleFactory { + + address public logicContract; + + /** + * @notice Constructor + * @param _polyAddress Address of the polytoken + */ + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public + ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + { + require(_logicContract != address(0), "Invalid address"); + version = "1.0.0"; + name = "VolumeRestrictionTM"; + title = "Volume Restriction Transfer Manager"; + description = "Manage transfers based on the volume of tokens that needs to be transact"; + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + logicContract = _logicContract; + } + + + /** + * @notice Used to launch the Module with the help of factory + * @return address Contract address of the Module + */ + function deploy(bytes /* _data */) external returns(address) { + if (setupCost > 0) + require(polyToken.transferFrom(msg.sender, owner, setupCost), "Insufficent Allowance"); + address volumeRestrictionTransferManager = new VolumeRestrictionTMProxy(msg.sender, address(polyToken), logicContract); + /*solium-disable-next-line security/no-block-members*/ + emit GenerateModuleFromFactory(volumeRestrictionTransferManager, getName(), address(this), msg.sender, setupCost, now); + return volumeRestrictionTransferManager; + } + + + /** + * @notice Type of the Module factory + */ + function getTypes() external view returns(uint8[]) { + uint8[] memory res = new uint8[](1); + res[0] = 2; + return res; + } + + /** + * @notice Returns the instructions associated with the module + */ + function getInstructions() external view returns(string) { + /*solium-disable-next-line max-len*/ + return "Module used to restrict the volume of tokens traded by the token holders"; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() public view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](5); + availableTags[0] = "Maximum Volume"; + availableTags[1] = "Transfer Restriction"; + availableTags[2] = "Daily Restriction"; + availableTags[3] = "Individual Restriction"; + availableTags[4] = "Default Restriction"; + return availableTags; + } + +} \ No newline at end of file diff --git a/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol new file mode 100644 index 000000000..e995676a0 --- /dev/null +++ b/contracts/modules/TransferManager/VolumeRestrictionTMStorage.sol @@ -0,0 +1,45 @@ +pragma solidity ^0.4.24; + +/** + * @title Storage layout for VolumeRestrictionTM + */ +contract VolumeRestrictionTMStorage { + + enum RestrictionType { Fixed, Percentage } + + 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 + // will be fixed amount of tokens + uint256 allowedTokens; + uint256 startTime; + uint256 rollingPeriodInDays; + uint256 endTime; + RestrictionType typeOfRestriction; + } + + struct BucketDetails { + uint256 lastTradedDayTime; + 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; + } + + // 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) + VolumeRestriction public defaultDailyRestriction; + // Restriction stored corresponds to a particular token holder + mapping(address => VolumeRestriction) public individualRestriction; + // Daily restriction stored corresponds to a particular token holder + mapping(address => VolumeRestriction) public individualDailyRestriction; + // Storing _from => day's timestamp => total amount transact in a day --individual + mapping(address => mapping(uint256 => uint256)) internal bucket; + // Storing the information that used to validate the transaction + mapping(address => BucketDetails) internal userToBucket; + // 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; + +} \ No newline at end of file diff --git a/contracts/proxy/VolumeRestrictionTMProxy.sol b/contracts/proxy/VolumeRestrictionTMProxy.sol new file mode 100644 index 000000000..e8c24e6be --- /dev/null +++ b/contracts/proxy/VolumeRestrictionTMProxy.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.24; + +import "../modules/TransferManager/VolumeRestrictionTMStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "../modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract VolumeRestrictionTMProxy is VolumeRestrictionTMStorage, ModuleStorage, Pausable, OwnedProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor (address _securityToken, address _polyAddress, address _implementation) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + __implementation = _implementation; + } + +} diff --git a/docs/permissions_list.md b/docs/permissions_list.md index 4a4438d5f..95775bf92 100644 --- a/docs/permissions_list.md +++ b/docs/permissions_list.md @@ -143,7 +143,7 @@ allocateTokensMulti() - TransferManager + TransferManager CountTransferManager changeHolderCount() withPerm(ADMIN) @@ -199,70 +199,46 @@ changeHolderPercentage() - LockupVolumeRestrictionTM - addLockup() - withPerm(ADMIN) + VolumeRestrictionTM + changeExemptWalletList() + withPerm(ADMIN) - addLockUpMulti() + addIndividualRestriction() - removeLockUp() + addIndividualRestrictionMulti() - modifyLockUp() + addGlobalRestriction() - SingleTradeVolumeRestrictionTM - setAllowPrimaryIssuance() - withPerm(ADMIN) - - - changeTransferLimitToPercentage() - - - changeTransferLimitToTokens() - - - changeGlobalLimitInTokens() - - - changeGlobalLimitInPercentage() - - - addExemptWallet() - - - removeExemptWallet() - - - addExemptWalletMulti() - - - removeExemptWalletMulti() + addDailyGlobalRestriction() - setTransferLimitInTokens() + removeIndividualRestriction() - setTransferLimitInPercentage() + removeIndividualRestrictionMulti() - removeTransferLimitInPercentage() + removeGlobalRestriction() - removeTransferLimitInTokens() + removeDailyGlobalRestriction() - setTransferLimitInTokensMulti() + modifyIndividualRestriction() - setTransferLimitInPercentageMulti() + modifyIndividualRestrictionMulti() - removeTransferLimitInTokensMulti() + modifyGlobalRestriction() + modifyDailyGlobalRestriction() + removeTransferLimitInPercentageMulti diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 4beb28252..09853f1d7 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -18,6 +18,8 @@ const CappedSTOFactory = artifacts.require('./CappedSTOFactory.sol') const USDTieredSTOFactory = artifacts.require('./USDTieredSTOFactory.sol') const SecurityTokenRegistry = artifacts.require('./SecurityTokenRegistry.sol') const SecurityTokenRegistryProxy = artifacts.require('./SecurityTokenRegistryProxy.sol') +const VolumeRestrictionTMFactory = artifacts.require('./VolumeRestrictionTMFactory.sol') +const VolumeRestrictionTMLogic = artifacts.require('./VolumeRestrictionTM.sol'); const FeatureRegistry = artifacts.require('./FeatureRegistry.sol') const STFactory = artifacts.require('./tokens/STFactory.sol') const DevPolyToken = artifacts.require('./helpers/PolyTokenFaucet.sol') @@ -46,17 +48,17 @@ module.exports = function (deployer, network, accounts) { web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) PolymathAccount = accounts[0] PolyToken = DevPolyToken.address // Development network polytoken address - deployer.deploy(DevPolyToken, {from: PolymathAccount}).then(() => { + deployer.deploy(DevPolyToken, { from: PolymathAccount }).then(() => { DevPolyToken.deployed().then((mockedUSDToken) => { UsdToken = mockedUSDToken.address; }); }); - deployer.deploy(MockOracle, PolyToken, "POLY", "USD", new BigNumber(0.5).times(new BigNumber(10).pow(18)), {from: PolymathAccount}).then(() => { + deployer.deploy(MockOracle, PolyToken, "POLY", "USD", new BigNumber(0.5).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { MockOracle.deployed().then((mockedOracle) => { POLYOracle = mockedOracle.address; }); }); - deployer.deploy(MockOracle, 0, "ETH", "USD", new BigNumber(500).times(new BigNumber(10).pow(18)), {from: PolymathAccount}).then(() => { + deployer.deploy(MockOracle, 0, "ETH", "USD", new BigNumber(500).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { MockOracle.deployed().then((mockedOracle) => { ETHOracle = mockedOracle.address; }); @@ -78,12 +80,12 @@ module.exports = function (deployer, network, accounts) { web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')) PolymathAccount = accounts[0] PolyToken = DevPolyToken.address // Development network polytoken address - deployer.deploy(MockOracle, PolyToken, "POLY", "USD", new BigNumber(0.5).times(new BigNumber(10).pow(18)), {from: PolymathAccount}).then(() => { + deployer.deploy(MockOracle, PolyToken, "POLY", "USD", new BigNumber(0.5).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { MockOracle.deployed().then((mockedOracle) => { POLYOracle = mockedOracle.address; }); }); - deployer.deploy(MockOracle, 0, "ETH", "USD", new BigNumber(500).times(new BigNumber(10).pow(18)), {from: PolymathAccount}).then(() => { + deployer.deploy(MockOracle, 0, "ETH", "USD", new BigNumber(500).times(new BigNumber(10).pow(18)), { from: PolymathAccount }).then(() => { MockOracle.deployed().then((mockedOracle) => { ETHOracle = mockedOracle.address; }); @@ -94,23 +96,23 @@ module.exports = function (deployer, network, accounts) { name: 'initialize', type: 'function', inputs: [{ - type:'address', - name: '_polymathRegistry' - },{ - type: 'address', - name: '_STFactory' - },{ - type: 'uint256', - name: '_stLaunchFee' - },{ - type: 'uint256', - name: '_tickerRegFee' - },{ - type: 'address', - name: '_polyToken' - },{ - type: 'address', - name: '_owner' + type: 'address', + name: '_polymathRegistry' + }, { + type: 'address', + name: '_STFactory' + }, { + type: 'uint256', + name: '_stLaunchFee' + }, { + type: 'uint256', + name: '_tickerRegFee' + }, { + type: 'address', + name: '_polyToken' + }, { + type: 'address', + name: '_owner' }] }; @@ -118,216 +120,233 @@ module.exports = function (deployer, network, accounts) { name: 'initialize', type: 'function', inputs: [{ - type:'address', - name: '_polymathRegistry' - },{ - type: 'address', - name: '_owner' + type: 'address', + name: '_polymathRegistry' + }, { + type: 'address', + name: '_owner' }] }; // POLYMATH NETWORK Configuration :: DO THIS ONLY ONCE // A) Deploy the PolymathRegistry contract - return deployer.deploy(PolymathRegistry, {from: PolymathAccount}).then(() => { + return deployer.deploy(PolymathRegistry, { from: PolymathAccount }).then(() => { return PolymathRegistry.deployed(); }).then((_polymathRegistry) => { polymathRegistry = _polymathRegistry; - return polymathRegistry.changeAddress("PolyToken", PolyToken, {from: PolymathAccount}); + return polymathRegistry.changeAddress("PolyToken", PolyToken, { from: PolymathAccount }); }).then(() => { // Deploy libraries - return deployer.deploy(TokenLib, {from: PolymathAccount}); + return deployer.deploy(TokenLib, { from: PolymathAccount }); }).then(() => { // Link libraries deployer.link(TokenLib, SecurityToken); deployer.link(TokenLib, STFactory); // A) Deploy the ModuleRegistry Contract (It contains the list of verified ModuleFactory) - return deployer.deploy(ModuleRegistry, {from: PolymathAccount}); + return deployer.deploy(ModuleRegistry, { from: PolymathAccount }); }).then(() => { - return deployer.deploy(ModuleRegistryProxy, {from: PolymathAccount}); + return deployer.deploy(ModuleRegistryProxy, { from: PolymathAccount }); }).then(() => { let bytesProxyMR = web3.eth.abi.encodeFunctionCall(functionSignatureProxyMR, [polymathRegistry.address, PolymathAccount]); - return ModuleRegistryProxy.at(ModuleRegistryProxy.address).upgradeToAndCall("1.0.0", ModuleRegistry.address, bytesProxyMR, {from: PolymathAccount}); + return ModuleRegistryProxy.at(ModuleRegistryProxy.address).upgradeToAndCall("1.0.0", ModuleRegistry.address, bytesProxyMR, { from: PolymathAccount }); }).then(() => { moduleRegistry = ModuleRegistry.at(ModuleRegistryProxy.address); // Add module registry to polymath registry - return polymathRegistry.changeAddress("ModuleRegistry", ModuleRegistryProxy.address, {from: PolymathAccount}); + return polymathRegistry.changeAddress("ModuleRegistry", ModuleRegistryProxy.address, { from: PolymathAccount }); }).then(() => { // B) Deploy the GeneralTransferManagerLogic Contract (Factory used to generate the GeneralTransferManager contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(GeneralTransferManagerLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); + return deployer.deploy(GeneralTransferManagerLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); }).then(() => { // B) Deploy the ERC20DividendCheckpointLogic Contract (Factory used to generate the ERC20DividendCheckpoint contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(ERC20DividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); + return deployer.deploy(ERC20DividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); }).then(() => { // B) Deploy the EtherDividendCheckpointLogic Contract (Factory used to generate the EtherDividendCheckpoint contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(EtherDividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); + return deployer.deploy(EtherDividendCheckpointLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); }).then(() => { // B) Deploy the VestingEscrowWalletLogic Contract (Factory used to generate the VestingEscrowWallet contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(VestingEscrowWalletLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); + return deployer.deploy(VestingEscrowWalletLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); }).then(() => { // B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", {from: PolymathAccount}); + return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); + }).then(() => { + // B) Deploy the VolumeRestrictionTMLogic Contract (Factory used to generate the VolumeRestrictionTM contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(VolumeRestrictionTMLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); }).then(() => { // B) Deploy the VestingEscrowWalletFactory Contract (Factory used to generate the VestingEscrowWallet contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(VestingEscrowWalletFactory, PolyToken, 0, 0, 0, VestingEscrowWalletLogic.address, {from: PolymathAccount}); + return deployer.deploy(VestingEscrowWalletFactory, PolyToken, 0, 0, 0, VestingEscrowWalletLogic.address, { from: PolymathAccount }); }).then(() => { // B) Deploy the GeneralTransferManagerFactory Contract (Factory used to generate the GeneralTransferManager contract and this // manager attach with the securityToken contract at the time of deployment) - return deployer.deploy(GeneralTransferManagerFactory, PolyToken, 0, 0, 0, GeneralTransferManagerLogic.address, {from: PolymathAccount}); + return deployer.deploy(GeneralTransferManagerFactory, PolyToken, 0, 0, 0, GeneralTransferManagerLogic.address, { 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) - return deployer.deploy(GeneralPermissionManagerFactory, PolyToken, 0, 0, 0, {from: PolymathAccount}); + return deployer.deploy(GeneralPermissionManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).then(() => { // D) Deploy the CountTransferManagerFactory Contract (Factory used to generate the CountTransferManager contract use // to track the counts of the investors of the security token) - return deployer.deploy(CountTransferManagerFactory, PolyToken, 0, 0, 0, {from: PolymathAccount}); + return deployer.deploy(CountTransferManagerFactory, 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) - return deployer.deploy(PercentageTransferManagerFactory, PolyToken, 0, 0, 0, {from: PolymathAccount}); + return deployer.deploy(PercentageTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).then(() => { // D) Deploy the EtherDividendCheckpointFactory Contract (Factory used to generate the EtherDividendCheckpoint contract use // to provide the functionality of the dividend in terms of ETH) - return deployer.deploy(EtherDividendCheckpointFactory, PolyToken, 0, 0, 0, EtherDividendCheckpointLogic.address, {from: PolymathAccount}); + return deployer.deploy(EtherDividendCheckpointFactory, PolyToken, 0, 0, 0, EtherDividendCheckpointLogic.address, { from: PolymathAccount }); }).then(() => { // D) Deploy the ERC20DividendCheckpointFactory Contract (Factory used to generate the ERC20DividendCheckpoint contract use // to provide the functionality of the dividend in terms of ERC20 token) - return deployer.deploy(ERC20DividendCheckpointFactory, PolyToken, 0, 0, 0, ERC20DividendCheckpointLogic.address, {from: PolymathAccount}); + return deployer.deploy(ERC20DividendCheckpointFactory, PolyToken, 0, 0, 0, ERC20DividendCheckpointLogic.address, { from: PolymathAccount }); + }).then(() => { + // D) Deploy the VolumeRestrictionTMFactory Contract (Factory used to generate the VolumeRestrictionTM contract use + // to provide the functionality of restricting the token volume) + return deployer.deploy(VolumeRestrictionTMFactory, PolyToken, 0, 0, 0, VolumeRestrictionTMLogic.address, { from: PolymathAccount }); }).then(() => { - // D) Deploy the ManualApprovalTransferManagerFactory Contract (Factory used to generate the ManualApprovalTransferManager contract use - // to manual approve the transfer that will overcome the other transfer restrictions) - return deployer.deploy(ManualApprovalTransferManagerFactory, PolyToken, 0, 0, 0, {from: PolymathAccount}); + // D) Deploy the ManualApprovalTransferManagerFactory Contract (Factory used to generate the ManualApprovalTransferManager contract use + // to manual approve the transfer that will overcome the other transfer restrictions) + return deployer.deploy(ManualApprovalTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).then(() => { // H) Deploy the STVersionProxy001 Contract which contains the logic of deployment of securityToken. - return deployer.deploy(STFactory, GeneralTransferManagerFactory.address, {from: PolymathAccount}); + return deployer.deploy(STFactory, GeneralTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // K) Deploy the FeatureRegistry contract to control feature switches - return deployer.deploy(FeatureRegistry, PolymathRegistry.address, {from: PolymathAccount}); + return deployer.deploy(FeatureRegistry, PolymathRegistry.address, { from: PolymathAccount }); }).then(() => { - // Assign the address into the FeatureRegistry key - return polymathRegistry.changeAddress("FeatureRegistry", FeatureRegistry.address, {from: PolymathAccount}); + // Assign the address into the FeatureRegistry key + return polymathRegistry.changeAddress("FeatureRegistry", FeatureRegistry.address, { from: PolymathAccount }); }).then(() => { // J) Deploy the SecurityTokenRegistry contract (Used to hold the deployed secuirtyToken details. It also act as the interface to deploy the SecurityToken) - return deployer.deploy(SecurityTokenRegistry, {from: PolymathAccount}) - }).then(()=> { - return deployer.deploy(SecurityTokenRegistryProxy, {from: PolymathAccount}); + return deployer.deploy(SecurityTokenRegistry, { from: PolymathAccount }) + }).then(() => { + return deployer.deploy(SecurityTokenRegistryProxy, { from: PolymathAccount }); }).then(() => { let bytesProxy = web3.eth.abi.encodeFunctionCall(functionSignatureProxy, [PolymathRegistry.address, STFactory.address, initRegFee, initRegFee, PolyToken, PolymathAccount]); - return SecurityTokenRegistryProxy.at(SecurityTokenRegistryProxy.address).upgradeToAndCall("1.0.0", SecurityTokenRegistry.address, bytesProxy, {from: PolymathAccount}); + return SecurityTokenRegistryProxy.at(SecurityTokenRegistryProxy.address).upgradeToAndCall("1.0.0", SecurityTokenRegistry.address, bytesProxy, { from: PolymathAccount }); }).then(() => { // Assign the address into the SecurityTokenRegistry key - return polymathRegistry.changeAddress("SecurityTokenRegistry", SecurityTokenRegistryProxy.address, {from: PolymathAccount}); + return polymathRegistry.changeAddress("SecurityTokenRegistry", SecurityTokenRegistryProxy.address, { from: PolymathAccount }); }).then(() => { // Update all addresses into the registry contract by calling the function updateFromregistry - return moduleRegistry.updateFromRegistry({from: PolymathAccount}); + return moduleRegistry.updateFromRegistry({ 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. - return moduleRegistry.registerModule(PercentageTransferManagerFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(PercentageTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // D) Register the VestingEscrowWalletFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the VestingEscrowWallet contract. - return moduleRegistry.registerModule(VestingEscrowWalletFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(VestingEscrowWalletFactory.address, { from: PolymathAccount }); }).then(() => { // D) Register the CountTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the CountTransferManager contract. - return moduleRegistry.registerModule(CountTransferManagerFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(CountTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // D) Register the GeneralTransferManagerFactory 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(GeneralTransferManagerFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(GeneralTransferManagerFactory.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. - return moduleRegistry.registerModule(GeneralPermissionManagerFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(GeneralPermissionManagerFactory.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. - return moduleRegistry.registerModule(EtherDividendCheckpointFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(EtherDividendCheckpointFactory.address, { from: PolymathAccount }); + }).then(() => { + // D) Register the VolumeRestrictionTMFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the VolumeRestrictionTM contract. + return moduleRegistry.registerModule(VolumeRestrictionTMFactory.address, { from: PolymathAccount }); }).then(() => { // D) Register the ManualApprovalTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the ManualApprovalTransferManager contract. - return moduleRegistry.registerModule(ManualApprovalTransferManagerFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(ManualApprovalTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // E) Register the ERC20DividendCheckpointFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the ERC20DividendCheckpoint contract. - return moduleRegistry.registerModule(ERC20DividendCheckpointFactory.address, {from: PolymathAccount}); + return moduleRegistry.registerModule(ERC20DividendCheckpointFactory.address, { from: PolymathAccount }); }).then(() => { // F) Once the GeneralTransferManagerFactory 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(GeneralTransferManagerFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(GeneralTransferManagerFactory.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}); + return moduleRegistry.verifyModule(CountTransferManagerFactory.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. // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. - return moduleRegistry.verifyModule(PercentageTransferManagerFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(PercentageTransferManagerFactory.address, true, { from: PolymathAccount }); }).then(() => { // G) Once the GeneralPermissionManagerFactory 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(GeneralPermissionManagerFactory.address, true, {from: PolymathAccount}) + return moduleRegistry.verifyModule(GeneralPermissionManagerFactory.address, true, { from: PolymathAccount }) }).then(() => { // G) Once the EtherDividendCheckpointFactory 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(EtherDividendCheckpointFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(EtherDividendCheckpointFactory.address, true, { from: PolymathAccount }); }).then(() => { // G) Once the ERC20DividendCheckpointFactory 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(ERC20DividendCheckpointFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(ERC20DividendCheckpointFactory.address, true, { from: PolymathAccount }); + }).then(() => { + // G) Once the VolumeRestrictionTMFactory 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(VolumeRestrictionTMFactory.address, true, { from: PolymathAccount }); }).then(() => { // G) Once the ManualApprovalTransferManagerFactory 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(ManualApprovalTransferManagerFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(ManualApprovalTransferManagerFactory.address, true, { from: PolymathAccount }); }).then(() => { // F) Once the VestingEscrowWalletFactory 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(VestingEscrowWalletFactory.address, true, {from: PolymathAccount}); + return moduleRegistry.verifyModule(VestingEscrowWalletFactory.address, true, { from: PolymathAccount }); }).then(() => { // M) Deploy the CappedSTOFactory (Use to generate the CappedSTO contract which will used to collect the funds ). - return deployer.deploy(CappedSTOFactory, PolyToken, cappedSTOSetupCost, 0, 0, {from: PolymathAccount}) + return deployer.deploy(CappedSTOFactory, PolyToken, cappedSTOSetupCost, 0, 0, { from: PolymathAccount }) }).then(() => { // N) Register the CappedSTOFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the CappedSTOFactory contract. - return moduleRegistry.registerModule(CappedSTOFactory.address, {from: PolymathAccount}) - }).then(()=>{ + return moduleRegistry.registerModule(CappedSTOFactory.address, { from: PolymathAccount }) + }).then(() => { // G) Once the CappedSTOFactory 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(CappedSTOFactory.address, true, {from: PolymathAccount}) + return moduleRegistry.verifyModule(CappedSTOFactory.address, true, { from: PolymathAccount }) }).then(() => { // H) Deploy the USDTieredSTOFactory (Use to generate the USDTieredSTOFactory contract which will used to collect the funds ). - return deployer.deploy(USDTieredSTOFactory, PolyToken, usdTieredSTOSetupCost, 0, 0, USDTieredSTOLogic.address, {from: PolymathAccount}) + return deployer.deploy(USDTieredSTOFactory, PolyToken, usdTieredSTOSetupCost, 0, 0, USDTieredSTOLogic.address, { from: PolymathAccount }) }).then(() => { // I) Register the USDTieredSTOFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the USDTieredSTOFactory contract. - return moduleRegistry.registerModule(USDTieredSTOFactory.address, {from: PolymathAccount}) - }).then(()=>{ + return moduleRegistry.registerModule(USDTieredSTOFactory.address, { from: PolymathAccount }) + }).then(() => { // J) Once the USDTieredSTOFactory 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(USDTieredSTOFactory.address, true, {from: PolymathAccount}) + return moduleRegistry.verifyModule(USDTieredSTOFactory.address, true, { from: PolymathAccount }) }).then(() => { - return polymathRegistry.changeAddress("PolyUsdOracle", POLYOracle, {from: PolymathAccount}); + return polymathRegistry.changeAddress("PolyUsdOracle", POLYOracle, { from: PolymathAccount }); }).then(() => { - return polymathRegistry.changeAddress("EthUsdOracle", ETHOracle, {from: PolymathAccount}); + return polymathRegistry.changeAddress("EthUsdOracle", ETHOracle, { from: PolymathAccount }); }).then(() => { - return deployer.deploy(SecurityToken, 'a', 'a', 18, 1, 'a', polymathRegistry.address, {from: PolymathAccount}); + return deployer.deploy(SecurityToken, 'a', 'a', 18, 1, 'a', polymathRegistry.address, { from: PolymathAccount }); }).then(() => { console.log('\n'); console.log(` @@ -356,6 +375,8 @@ module.exports = function (deployer, network, accounts) { ERC20DividendCheckpointLogic: ${ERC20DividendCheckpointLogic.address} EtherDividendCheckpointFactory: ${EtherDividendCheckpointFactory.address} ERC20DividendCheckpointFactory: ${ERC20DividendCheckpointFactory.address} + VolumeRestrictionTMFactory: ${VolumeRestrictionTMFactory.address} + VolumeRestrictionTMLogic: ${VolumeRestrictionTMLogic.address} VestingEscrowWalletFactory: ${VestingEscrowWalletFactory.address} VestingEscrowWalletLogic: ${VestingEscrowWalletLogic.address} --------------------------------------------------------------------------------- diff --git a/test/d_count_transfer_manager.js b/test/d_count_transfer_manager.js index c3cfa19e8..3107c4bf7 100644 --- a/test/d_count_transfer_manager.js +++ b/test/d_count_transfer_manager.js @@ -352,119 +352,108 @@ contract("CountTransferManager", accounts => { let perm = await I_CountTransferManager.getPermissions.call(); assert.equal(perm.length, 1); }); - - describe("Test cases for adding and removing acc holder at the same time", async () => { - it("deploy a new token & auto attach modules", async () => { - - //register ticker and deploy token - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol2, contact, { from: token_owner }); - - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx2 = await I_STRProxied.generateSecurityToken(name, symbol2, tokenDetails, false, { from: token_owner }); - - I_SecurityToken2 = SecurityToken.at(tx2.logs[1].args._securityTokenAddress); - - let moduleData = (await I_SecurityToken2.getModulesByType(2))[0]; - I_GeneralTransferManager2 = GeneralTransferManager.at(moduleData); - }); - - it("add 3 holders to the token", async () => { - await I_GeneralTransferManager2.modifyWhitelist( - account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - - await I_GeneralTransferManager2.modifyWhitelist( - account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - - await I_GeneralTransferManager2.modifyWhitelist( - account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - - await I_GeneralTransferManager2.modifyWhitelist( - account_investor4, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer, - gas: 500000 - } - ); - - // Jump time - await increaseTime(5000); - - // Add 3 holders to the token - await I_SecurityToken2.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); - await I_SecurityToken2.mint(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner }); - await I_SecurityToken2.mint(account_investor3, web3.utils.toWei("1", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken2.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); - }); - - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken2.getModulesByType(2))[0]; - I_GeneralTransferManager2 = GeneralTransferManager.at(moduleData); - }); - - it("Should successfully attach the CountTransferManager factory with the security token", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); - await catchRevert( - I_SecurityToken2.addModule(P_CountTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { - from: token_owner - }) - ); - }); - - it("Should successfully attach the CountTransferManager with the security token and set max holder to 2", async () => { - const tx = await I_SecurityToken2.addModule(I_CountTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), - "CountTransferManager", - "CountTransferManager module was not added" - ); - I_CountTransferManager2 = CountTransferManager.at(tx.logs[2].args._module); - await I_CountTransferManager2.changeHolderCount(2, {from: token_owner}); - console.log('current max holder number is '+ await I_CountTransferManager2.maxHolderCount({from: token_owner})); - }); - - it("Should allow add a new token holder while transfer all the tokens at one go", async () => { - let amount = (await I_SecurityToken2.balanceOf(account_investor2)).toNumber(); - let investorCount = await I_SecurityToken2.getInvestorCount({from: account_investor2 }); - console.log("current investor count is " + investorCount); - await I_SecurityToken2.transfer(account_investor4, amount, { from: account_investor2 }); - assert((await I_SecurityToken2.balanceOf(account_investor4)).toNumber(), amount, {from: account_investor2 }); - assert(await I_SecurityToken2.getInvestorCount({from: account_investor2 }), investorCount); - }); - }); + describe("Test cases for adding and removing acc holder at the same time", async () => { + it("deploy a new token & auto attach modules", async () => { + + //register ticker and deploy token + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol2, contact, { from: token_owner }); + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx2 = await I_STRProxied.generateSecurityToken(name, symbol2, tokenDetails, false, { from: token_owner }); + + I_SecurityToken2 = SecurityToken.at(tx2.logs[1].args._securityTokenAddress); + let moduleData = (await I_SecurityToken2.getModulesByType(2))[0]; + I_GeneralTransferManager2 = GeneralTransferManager.at(moduleData); + }); + it("add 3 holders to the token", async () => { + await I_GeneralTransferManager2.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 500000 + } + ); + await I_GeneralTransferManager2.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 500000 + } + ); + await I_GeneralTransferManager2.modifyWhitelist( + account_investor3, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 500000 + } + ); + + await I_GeneralTransferManager2.modifyWhitelist( + account_investor4, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer, + gas: 500000 + } + ); + + // Jump time + await increaseTime(5000); + // Add 3 holders to the token + await I_SecurityToken2.mint(account_investor1, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken2.mint(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken2.mint(account_investor3, web3.utils.toWei("1", "ether"), { from: token_owner }); + assert.equal((await I_SecurityToken2.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + }); + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken2.getModulesByType(2))[0]; + I_GeneralTransferManager2 = GeneralTransferManager.at(moduleData); + }); + it("Should successfully attach the CountTransferManager factory with the security token", async () => { + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await catchRevert( + I_SecurityToken2.addModule(P_CountTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { + from: token_owner + }) + ); + }); + it("Should successfully attach the CountTransferManager with the security token and set max holder to 2", async () => { + const tx = await I_SecurityToken2.addModule(I_CountTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "CountTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "CountTransferManager", + "CountTransferManager module was not added" + ); + I_CountTransferManager2 = CountTransferManager.at(tx.logs[2].args._module); + await I_CountTransferManager2.changeHolderCount(2, {from: token_owner}); + console.log('current max holder number is '+ await I_CountTransferManager2.maxHolderCount({from: token_owner})); + }); + it("Should allow add a new token holder while transfer all the tokens at one go", async () => { + let amount = (await I_SecurityToken2.balanceOf(account_investor2)).toNumber(); + let investorCount = await I_SecurityToken2.getInvestorCount({from: account_investor2 }); + console.log("current investor count is " + investorCount); + await I_SecurityToken2.transfer(account_investor4, amount, { from: account_investor2 }); + assert((await I_SecurityToken2.balanceOf(account_investor4)).toNumber(), amount, {from: account_investor2 }); + assert(await I_SecurityToken2.getInvestorCount({from: account_investor2 }), investorCount); + }); + }); describe("Test cases for the factory", async () => { it("should get the exact details of the factory", async () => { diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index a6f495984..f2d9d41d2 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -32,6 +32,8 @@ const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); const DummySTOFactory = artifacts.require("./DummySTOFactory.sol"); const MockBurnFactory = artifacts.require("./MockBurnFactory.sol"); const MockWrongTypeFactory = artifacts.require("./MockWrongTypeFactory.sol"); +const VolumeRestrictionTMFactory = artifacts.require("./VolumeRestrictionTMFactory.sol"); +const VolumeRestrictionTM = artifacts.require("./VolumeRestrictionTM.sol"); const VestingEscrowWalletFactory = artifacts.require("./VestingEscrowWalletFactory.sol"); const VestingEscrowWallet = artifacts.require("./VestingEscrowWallet.sol"); @@ -53,11 +55,13 @@ let I_EtherDividendCheckpointFactory; let I_CountTransferManagerFactory; let I_ERC20DividendCheckpointLogic; let I_ERC20DividendCheckpointFactory; +let I_VolumeRestrictionTMFactory; let I_GeneralPermissionManagerFactory; let I_GeneralTransferManagerLogic; let I_GeneralTransferManagerFactory; let I_VestingEscrowWalletFactory; let I_GeneralTransferManager; +let I_VolumeRestrictionTMLogic; let I_ModuleRegistryProxy; let I_PreSaleSTOFactory; let I_ModuleRegistry; @@ -219,7 +223,7 @@ export async function deployGTMAndVerifyed(accountPolymath, MRProxyInstance, pol I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(polyToken, setupCost, 0, 0, I_GeneralTransferManagerLogic.address, { from: accountPolymath }); assert.notEqual( - I_GeneralPermissionManagerFactory.address.valueOf(), + I_GeneralTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", "GeneralPermissionManagerFactory contract was not deployed" ); @@ -229,6 +233,22 @@ export async function deployGTMAndVerifyed(accountPolymath, MRProxyInstance, pol return new Array(I_GeneralTransferManagerFactory); } +export async function deployVRTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { + I_VolumeRestrictionTMLogic = await VolumeRestrictionTM.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + + I_VolumeRestrictionTMFactory = await VolumeRestrictionTMFactory.new(polyToken, setupCost, 0, 0, I_VolumeRestrictionTMLogic.address, { from: accountPolymath }); + + assert.notEqual( + I_VolumeRestrictionTMFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "VolumeRestrictionTMFactory contract was not deployed" + ); + + // (B) : Register the GeneralDelegateManagerFactory + await registerAndVerifyByMR(I_VolumeRestrictionTMFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_VolumeRestrictionTMFactory); +} + export async function deployCountTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { I_CountTransferManagerFactory = await CountTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); diff --git a/test/y_scheduled_checkpoints.js b/test/x_scheduled_checkpoints.js similarity index 100% rename from test/y_scheduled_checkpoints.js rename to test/x_scheduled_checkpoints.js diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js new file mode 100644 index 000000000..1075a64e4 --- /dev/null +++ b/test/y_volume_restriction_tm.js @@ -0,0 +1,1556 @@ +import latestTime from './helpers/latestTime'; +import {signData} from './helpers/signData'; +import { pk } from './helpers/testprivateKey'; +import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; +import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; +import { catchRevert } from "./helpers/exceptions"; +import { setUpPolymathNetwork, deployVRTMAndVerifyed } from "./helpers/createInstances"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager.sol'); +const VolumeRestrictionTM = artifacts.require('./VolumeRestrictionTM.sol'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('VolumeRestrictionTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let token_owner_pk; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + let account_delegate; + let account_delegate2; + let account_delegate3; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_VolumeRestrictionTMFactory; + let P_VolumeRestrictionTMFactory; + let I_SecurityTokenRegistryProxy; + let P_VolumeRestrictionTM; + let I_GeneralTransferManagerFactory; + let I_VolumeRestrictionTM; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_DummySTOFactory; + let I_STFactory; + let I_SecurityToken; + let I_MRProxied; + let I_STRProxied; + 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"; + const delegateDetails = "Hello I am legit delegate"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + let tempAmount = new BigNumber(0); + let tempArray = new Array(); + let tempArray3 = new Array(); + let tempArrayGlobal = new Array(); + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + async function print(data, account) { + console.log(` + Latest timestamp: ${data[0].toNumber()} + SumOfLastPeriod: ${data[1].dividedBy(new BigNumber(10).pow(18)).toNumber()} + Days Covered: ${data[2].toNumber()} + Latest timestamp daily: ${data[3].toNumber()} + Individual Total Trade on latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[0])) + .dividedBy(new BigNumber(10).pow(18)).toNumber()} + Individual Total Trade on daily latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[3])) + .dividedBy(new BigNumber(10).pow(18)).toNumber()} + `) + } + + async function calculateSum(rollingPeriod, tempArray) { + let sum = 0; + let start = 0; + if (tempArray.length >= rollingPeriod) + start = tempArray.length - rollingPeriod; + for (let i = start; i < tempArray.length; i++) { + sum += tempArray[i]; + } + return sum; + } + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + token_owner_pk = pk.account_1; + + account_investor1 = accounts[8]; + account_investor2 = accounts[9]; + account_investor3 = accounts[4]; + account_investor4 = accounts[3]; + account_delegate = accounts[7]; + account_delegate2 = accounts[6]; + account_delegate3 = accounts[5]; + + // Step 1: Deploy the genral PM ecosystem + 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 5: Deploy the VolumeRestrictionTMFactory + [I_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 6: Deploy the VolumeRestrictionTMFactory + [P_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(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} + ModuleRegistryProxy ${I_ModuleRegistryProxy.address} + ModuleRegistry: ${I_ModuleRegistry.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + VolumeRestrictionTMFactory: ${I_VolumeRestrictionTMFactory.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, true, { 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("Attach the VRTM", async() => { + it("Deploy the VRTM and attach with the ST", async()=> { + let tx = await I_SecurityToken.addModule(I_VolumeRestrictionTMFactory.address, 0, 0, 0, {from: token_owner }); + assert.equal(tx.logs[2].args._moduleFactory, I_VolumeRestrictionTMFactory.address); + assert.equal( + web3.utils.toUtf8(tx.logs[2].args._name), + "VolumeRestrictionTM", + "VolumeRestrictionTMFactory doesn not added"); + I_VolumeRestrictionTM = VolumeRestrictionTM.at(tx.logs[2].args._module); + }); + + it("Transfer some tokens to different account", async() => { + // Add tokens in to the whitelist + await I_GeneralTransferManager.modifyWhitelistMulti( + [account_investor1, account_investor2, account_investor3], + [latestTime(), latestTime(), latestTime()], + [latestTime(), latestTime(), latestTime()], + [latestTime() + duration.days(60), latestTime() + duration.days(60), latestTime() + duration.days(60)], + [true, true, true], + { + from: token_owner + } + ); + + // Mint some tokens and transferred to whitelisted addresses + 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 + let bal1 = await I_SecurityToken.balanceOf.call(account_investor1); + let bal2 = await I_SecurityToken.balanceOf.call(account_investor2); + // Verifying the balances + assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 40); + assert.equal(web3.utils.fromWei((bal2.toNumber()).toString()), 30); + + }); + + it("Should transfer the tokens freely without any restriction", async() => { + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('5', 'ether'), { from: account_investor1 }); + let bal1 = await I_SecurityToken.balanceOf.call(account_investor3); + // Verifying the balances + assert.equal(web3.utils.fromWei((bal1.toNumber()).toString()), 35); + }); + }) + + describe("Test for the addIndividualRestriction", async() => { + + it("Should add the restriction -- failed because of bad owner", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("12"), + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(10), + 0, + { + from: account_polymath + } + ) + ); + }) + + it("Should add the restriction -- failed because of bad parameters i.e invalid restriction type", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("12"), + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(10), + 3, + { + from: token_owner + } + ) + ); + }) + + it("Should add the restriction -- failed because of bad parameters i.e Invalid value of allowed tokens", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + 0, + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(10), + 0, + { + from: token_owner + } + ) + ); + }) + + it("Should add the restriction -- failed because of bad parameters i.e Percentage of tokens not within (0,100]", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + 0, + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(10), + 1, + { + from: token_owner + } + ) + ); + }) + + it("Should add the restriction -- failed because of bad parameters i.e Percentage of tokens not within (0,100]", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("10"), + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(10), + 1, + { + from: token_owner + } + ) + ); + }) + + it("Should add the restriction -- failed because of bad parameters i.e invalid dates", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("10"), + latestTime() - duration.seconds(5), + 3, + latestTime() + duration.days(10), + 0, + { + from: token_owner + } + ) + ); + }) + + it("Should add the restriction -- failed because of bad parameters i.e invalid dates", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("10"), + latestTime() + duration.days(2), + 3, + latestTime() + duration.days(1), + 0, + { + from: token_owner + } + ) + ); + }); + + it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("10"), + latestTime() + duration.days(2), + 0, + latestTime() + duration.days(10), + 0, + { + from: token_owner + } + ) + ); + }); + + it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("10"), + latestTime() + duration.days(2), + 366, + latestTime() + duration.days(10), + 0, + { + from: token_owner + } + ) + ); + }); + + it("Should add the restriction -- failed because of bad parameters i.e invalid rolling period", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("10"), + latestTime() + duration.days(2), + 3, + latestTime() + duration.days(3), + 0, + { + from: token_owner + } + ) + ); + }); + + it("Should add the restriction succesfully", async() => { + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("12"), + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(5), + 0, + { + from: token_owner + } + ); + + assert.equal(tx.logs[0].args._holder, account_investor1); + assert.equal(tx.logs[0].args._typeOfRestriction, 0); + }); + + it("Should add the restriction for multiple investor -- failed because of bad owner", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3,4,5], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [0,0,0], + { + from: account_polymath + } + ) + ) + }); + + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3,4,5], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [0,0,0], + { + from: token_owner + } + ) + ) + }); + + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3,4,5], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [0,0,0], + { + from: account_polymath + } + ) + ) + }); + + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3,4,5], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [0,0,0], + { + from: token_owner + } + ) + ) + }); + + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [0,0,0], + { + from: token_owner + } + ) + ) + }); + + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3, 4, 5], + [latestTime() + duration.days(5)], + [0,0,0], + { + from: token_owner + } + ) + ) + }); + + it("Should add the restriction for multiple investor -- failed because of bad parameters i.e length mismatch", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3, 4, 5], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [], + { + from: token_owner + } + ) + ) + }); + + it("Should add the restriction for multiple investor successfully", async() => { + await I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor2, account_delegate3, account_investor4], + [web3.utils.toWei("12"), web3.utils.toWei("10"), web3.utils.toWei("15")], + [latestTime() + duration.seconds(2), latestTime() + duration.seconds(2), latestTime() + duration.seconds(2)], + [3, 4, 5], + [latestTime() + duration.days(5), latestTime() + duration.days(6), latestTime() + duration.days(7)], + [0,0,0], + { + from: token_owner + } + ); + 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); + }); + + it("Should remove the restriction multi -- failed because of address is 0", async() => { + await catchRevert( + I_VolumeRestrictionTM.removeIndividualRestrictionMulti( + [0, account_delegate3, account_investor4], + { + from: token_owner + } + ) + ); + }); + + 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); + }); + + it("Should remove the restriction -- failed because restriction not present anymore", async() => { + await catchRevert( + I_VolumeRestrictionTM.removeIndividualRestriction(account_investor2, {from: token_owner}) + ); + }); + + it("Should remove the restriction multi", async() => { + await I_VolumeRestrictionTM.removeIndividualRestrictionMulti( + [account_delegate3, account_investor4], + { + from: token_owner + } + ) + }); + + it("Should add the restriction succesfully after the expiry of previous one for investor 1", async() => { + await increaseTime(duration.days(5.1)); + + console.log( + `Estimated gas for addIndividualRestriction: + ${await I_VolumeRestrictionTM.addIndividualRestriction.estimateGas( + account_investor1, + web3.utils.toWei("12"), + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(6), + 0, + { + from: token_owner + } + )} + `); + + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("12"), + latestTime() + duration.seconds(2), + 3, + latestTime() + duration.days(6), + 0, + { + from: token_owner + } + ); + + assert.equal(tx.logs[1].args._holder, account_investor1); + assert.equal(tx.logs[1].args._typeOfRestriction, 0); + }); + + it("Should not successfully transact the tokens -- failed because volume is above the limit", async() => { + await increaseTime(duration.seconds(10)); + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei("13"), { from: account_investor1}) + ); + }); + + it("Should succesfully transact the tokens by investor 1 just after the startTime", async() => { + // Check the transfer will be valid or not by calling the verifyTransfer() directly by using _isTransfer = false + let result = await I_VolumeRestrictionTM.verifyTransfer.call(account_investor1, account_investor3, web3.utils.toWei('.3'), "0x0", false); + assert.equal(result.toNumber(), 1); + // Perform the transaction + console.log(` + 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 + 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(), + 0.3 + ); + assert.equal( + data[0].toNumber(), + (await I_VolumeRestrictionTM.individualRestriction.call(account_investor1))[1].toNumber() + ); + assert.equal(web3.utils.fromWei((data[1].toNumber()).toString()), 0.3); + tempArray.push(0.3); + }); + + it("Should fail to add the individual daily restriction -- Bad msg.sender", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor3, + web3.utils.toWei("6"), + latestTime() + duration.seconds(1), + latestTime() + duration.days(4), + 0, + { + from: account_investor1 + } + ) + ); + }) + + it("Should fail to add the individual daily restriction -- Bad params value", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor3, + web3.utils.toWei("6"), + latestTime() + duration.seconds(1), + latestTime() + duration.days(4), + 1, + { + from: token_owner + } + ) + ); + }) + + it("Should fail to add the individual daily restriction -- Bad params value", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor3, + 0, + latestTime() + duration.seconds(1), + latestTime() + duration.days(4), + 0, + { + from: token_owner + } + ) + ); + }) + + it("Should fail to add the individual daily restriction -- Bad params value", async() => { + await catchRevert( + I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor3, + web3.utils.toWei("6"), + latestTime() + duration.days(5), + latestTime() + duration.days(4), + 0, + { + from: token_owner + } + ) + ); + }) + + it("Should add the individual daily restriction for investor 3", async() => { + let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor3, + web3.utils.toWei("6"), + latestTime() + duration.seconds(1), + latestTime() + duration.days(4), + 0, + { + from: token_owner + } + ); + + 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 dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); + console.log(` + *** Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + }); + + it("Should transfer the tokens within the individual daily restriction limits", async() => { + // transfer 2 tokens as per the limit + await increaseTime(5); // increase 5 seconds to layoff the time gap + let startTime = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + console.log(` + Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("2"), {from: account_investor3})} + `) + 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(` + Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("4"), {from: account_investor3})} + `) + // transfer the 4 tokens which is under the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), {from: account_investor3}); + let newData = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3); + await print(newData, account_investor3); + + assert.equal(newData[3].toNumber(), data[3].toNumber()); + assert.equal(data[3].toNumber(), startTime); + assert.equal((await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[3])) + .dividedBy(new BigNumber(10).pow(18)).toNumber(), 6); + }); + + 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() => { + // increase the EVM time by one day + await increaseTime(duration.days(1)); + + let startTime = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + console.log(` + Gas Estimation for the Individual daily tx - ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("2"), {from: account_investor3})} + `) + 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); + + assert.equal(data[3].toNumber(), startTime + duration.days(1)); + assert.equal((await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[3])) + .dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); + }); + + it("Should add the daily restriction on the investor 1", async() => { + let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor1, + new BigNumber(5).times(new BigNumber(10).pow(16)), + 0, + latestTime() + duration.days(4), + 1, + { + from: token_owner + } + ); + + 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 dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1); + console.log(` + *** Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(16)).toNumber()} % of TotalSupply + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + }); + + it("Should transfer tokens on the 2nd day by investor1 (Individual + Individual daily)", async() => { + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor1))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor1))[2].toNumber(); + + console.log(` + Gas estimation (Individual + Individual daily): ${await I_SecurityToken.transfer.estimateGas(account_investor2, web3.utils.toWei("2"), {from: account_investor1})}` + ); + + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), {from: account_investor1}); + // 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); + tempArray.push(2); + + let data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor1); + await print(data, account_investor1); + + // get the trade amount using the timestamp + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor1, data[0].toNumber())) + .dividedBy(new BigNumber(10).pow(18)).toNumber(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray)); + assert.equal(data[2].toNumber(), 1); + assert.equal(data[3].toNumber(), + (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor1))[1].toNumber()); + assert.equal(amt, 2); + }); + + it("Should fail to transfer by investor 1 -- because voilating the individual daily", async() => { + // transfer 4 tokens -- voilate the daily restriction + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), {from: account_investor1}) + ); + }); + + it("Should add the individual restriction to investor 3", async() => { + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor3, + new BigNumber(15.36).times(new BigNumber(10).pow(16)), // 15.36 tokens as totalsupply is 1000 + latestTime() + duration.seconds(2), + 6, + latestTime() + duration.days(15), + 1, + { + from: token_owner + } + ); + + assert.equal(tx.logs[0].args._holder, account_investor3); + assert.equal(tx.logs[0].args._typeOfRestriction, 1); + }); + + it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async() => { + await increaseTime(4); + // Allowed 4 tokens to transfer + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTimeDaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + 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 + 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 + let bal2 = await I_SecurityToken.balanceOf.call(account_investor3); + // Verifying the balances + assert.equal(web3.utils.fromWei(((bal1.minus(bal2)).toNumber()).toString()), 4); + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), 4); + assert.equal(data[2].toNumber(), 0); + assert.equal(data[3].toNumber(), startTimeDaily + duration.days(1)); + assert.equal(amt, 4); + + }); + + it("Should fail during transferring more tokens by investor3 -- Voilating the daily Limit", async() => { + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1"), {from: account_investor3}) + ); + }); + + it("Should remove the daily individual limit and transfer more tokens on a same day -- failed because of bad owner", async() => { + // remove the Individual daily restriction + await catchRevert( + I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor3, {from: account_investor4}) + ); + }) + + it("Should remove the daily individual limit and transfer more tokens on a same day", async() => { + // 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 startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + + // transfer more tokens on the same day + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("4"), {from: account_investor3}); + tempArray3[tempArray3.length -1] += 4; + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), 8); + assert.equal(data[2].toNumber(), 0); + assert.equal(data[3].toNumber(), 0); + assert.equal(amt, 8); + }); + + it("Should add the new Individual daily restriction and transact the tokens", async() => { + // add new restriction + let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor3, + web3.utils.toWei("2"), + latestTime() + duration.days(1), + latestTime() + duration.days(4), + 0, + { + from: token_owner + } + ); + + 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("2")); + let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); + console.log(` + *** Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + // Increase the time by one day + await increaseTime(duration.days(1.1)); + + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), {from: account_investor3}); + tempArray3.push(2); + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 1); + assert.equal(data[3].toNumber(), dataRestriction[1].toNumber()); + assert.equal(amt, 2); + + // Fail to sell more tokens than the limit + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), {from: account_investor3}) + ); + }); + + it("Should fail to modify the individual daily restriction -- bad owner", async() => { + await catchRevert( + I_VolumeRestrictionTM.modifyIndividualDailyRestriction( + account_investor3, + web3.utils.toWei('3'), + latestTime(), + latestTime() + duration.days(5), + 0, + { + from: account_polymath + } + ) + ); + }); + + it("Should modify the individual daily restriction", async() => { + await I_VolumeRestrictionTM.modifyIndividualDailyRestriction( + account_investor3, + web3.utils.toWei('3'), + 0, + latestTime() + duration.days(5), + 0, + { + from: token_owner + } + ); + + let dataRestriction = await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3); + console.log(` + *** Modify Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + }); + + it("Should allow to sell to transfer more tokens by investor3", async() => { + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), {from: account_investor3}); + tempArray3[tempArray3.length -1] += 3; + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 1); + assert.equal(data[3].toNumber(), startTimedaily); + assert.equal(amt, 5); + }); + + it("Should allow to transact the tokens on the other day", async() => { + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + + await increaseTime(duration.days(1)); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2.36"), {from: account_investor3}); + tempArray3.push(2.36); + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 2); + assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(amt, 2.36); + }); + + it("Should fail to transfer the tokens after completion of the total amount", async() => { + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("0.3"), {from: account_investor3}) + ); + }) + + it("Should sell more tokens on the same day after changing the total supply", async() => { + await I_SecurityToken.mint(account_investor3, web3.utils.toWei("10"), {from: token_owner}); + + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei(".50"), {from: account_investor3}); + tempArray3[tempArray3.length -1] += .50; + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 2); + assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(amt, 2.86); + }); + + it("Should fail to transact tokens more than the allowed in the second rolling period", async() => { + await increaseTime(duration.days(4)); + let i + for (i = 0; i < 3; i++) { + tempArray3.push(0); + } + console.log(`Diff Days: ${(latestTime() - ((await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_investor3))[0]).toNumber()) / 86400}`); + let allowedAmount = (tempArray3[0] + 1.1); + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei(allowedAmount.toString()), {from: account_investor3}) + ); + }) + + it("Should successfully to transact tokens in the second rolling period", async() => { + // Should transact freely tokens daily limit is also ended + + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + 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); + + // 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 6); + assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(amt, allowedAmount); + }); + + it("Should sell more tokens on the net day of rolling period", async() => { + await increaseTime(duration.days(3)); + + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + + tempArray3.push(0); + tempArray3.push(0); + + //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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 9); + assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(amt, 7); + }) + + it("Should transfer after the 5 days", async() => { + await increaseTime(duration.days(4.5)); + + for (let i = 0; i <3; i++) { + tempArray3.push(0); + } + + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.individualDailyRestriction.call(account_investor3))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_investor3))[2].toNumber(); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("25"), {from: account_investor2}); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("8"), {from: account_investor3}); + tempArray3.push(8); + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 13); + assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(amt, 8); + }); + + it("Should freely transfer the tokens after one day (completion of individual restriction)", async() => { + // increase one time + await increaseTime(duration.days(2)); + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("17"), {from: account_investor3}); + }); + }); + + describe("Test cases for the Default restrictions", async() => { + + it("Should add the investor 4 in the whitelist", async() => { + await I_GeneralTransferManager.modifyWhitelist( + account_investor4, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: token_owner + } + ); + }); + + it("Should mint some tokens to investor 4", async() => { + await I_SecurityToken.mint(account_investor4, web3.utils.toWei("20"), {from: token_owner}); + }); + + it("Should add the default daily restriction successfully", async() => { + await I_VolumeRestrictionTM.addDefaultDailyRestriction( + new BigNumber(2.75).times(new BigNumber(10).pow(16)), + 0, + latestTime() + duration.days(3), + 1, + { + from: token_owner + } + ); + + let dataRestriction = await I_VolumeRestrictionTM.defaultDailyRestriction.call(); + console.log(` + *** Add Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(16)).toNumber()} % of TotalSupply + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + }); + + it("Should fail to transfer above the daily limit", async() => { + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei("5"), {from: account_investor4}) + ) + }) + + it("Should transfer the token by investor 4", async() => { + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), 0); + assert.equal(data[1].toNumber(), 0); + assert.equal(data[2].toNumber(), 0); + assert.equal(data[3].toNumber(), startTimedaily); + assert.equal(amt, 3.57); + }); + + it("Should transfer the tokens freely after ending the default daily restriction", async() => { + await increaseTime(duration.days(3) + 10); + //sell tokens upto the limit + let tx = await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("5"), {from: account_investor4}); + assert.equal((tx.logs[0].args.value).toNumber(), web3.utils.toWei("5")); + // Transfer the tokens again to investor 3 + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("40"), {from: account_investor2}); + }) + + it("Should successfully add the default restriction", async() => { + await I_VolumeRestrictionTM.addDefaultRestriction( + web3.utils.toWei("10"), + 0, + 5, + latestTime() + duration.days(10), + 0, + { + from: token_owner + } + ); + + let data = await I_VolumeRestrictionTM.defaultRestriction.call(); + assert.equal(data[0].toNumber(), web3.utils.toWei("10")); + assert.equal(data[2].toNumber(), 5); + let dataRestriction = await I_VolumeRestrictionTM.defaultRestriction.call(); + console.log(` + *** Add Individual restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + }); + + it("Should transfer tokens on by investor 3 (comes under the Default restriction)", async() => { + await increaseTime(10); + tempArray3.length = 0; + let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("5"), {from: account_investor3}); + tempArray3.push(5); + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 0); + assert.equal(data[3].toNumber(), 0); + assert.equal(amt, 5); + + // Transfer tokens on another day + await increaseTime(duration.days(1)); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), {from: account_investor3}); + tempArray3.push(3); + + data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3); + await print(data, account_investor3); + + // get the trade amount using the timestamp + amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_investor3, data[0].toNumber())) + .dividedBy(new BigNumber(10).pow(18)).toNumber(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 1); + assert.equal(data[3].toNumber(), 0); + assert.equal(amt, 3); + }); + + it("Should fail to transfer more tokens than the available default limit", async() => { + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), {from: account_investor3}) + ); + }); + + it("Should able to transfer tokens in the next rolling period", async() => { + await increaseTime(duration.days(4.1)); + console.log(`*** Diff days: ${(latestTime() - ((await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_investor3))[0]).toNumber()) / 86400}`) + for (let i = 0; i < 3; i++) { + tempArray3.push(0); + } + + let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + + //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.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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 5); + assert.equal(data[3].toNumber(), 0); + assert.equal(amt, 7); + + // Try to transact more on the same day but fail + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1"), {from: account_investor3}) + ); + }); + + it("Should add the daily default restriction again", async() => { + await I_VolumeRestrictionTM.addDefaultDailyRestriction( + web3.utils.toWei("2"), + 0, + latestTime() + duration.days(3), + 0, + { + from: token_owner + } + ); + + let dataRestriction = await I_VolumeRestrictionTM.defaultDailyRestriction.call(); + console.log(` + *** Add Individual Daily restriction data *** + Allowed Tokens: ${dataRestriction[0].dividedBy(new BigNumber(10).pow(16)).toNumber()} + StartTime : ${dataRestriction[1].toNumber()} + Rolling Period in days : ${dataRestriction[2].toNumber()} + EndTime : ${dataRestriction[3].toNumber()} + Type of Restriction: ${dataRestriction[4].toNumber()} + `); + }); + + it("Should not able to transfer tokens more than the default daily restriction", async() => { + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("3"), {from: account_investor3}) + ); + }); + + it("Should able to transfer tokens within the limit of (daily default + default) restriction", async() => { + await increaseTime(duration.days(1)); + let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); + let startTimedaily = (await I_VolumeRestrictionTM.defaultDailyRestriction.call())[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("2"), {from: account_investor3}); + tempArray3.push(2); + + 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(); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[1].dividedBy(new BigNumber(10).pow(18)).toNumber(), await calculateSum(rollingPeriod, tempArray3)); + assert.equal(data[2].toNumber(), 6); + assert.equal(data[3].toNumber(), startTimedaily + duration.days(1)); + assert.equal(amt, 2); + }); + }) + + describe("Test for the exemptlist", async() => { + + it("Should add the token holder in the exemption list -- failed because of bad owner", async() => { + await catchRevert( + I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, {from: account_polymath}) + ); + }); + + it("Should add the token holder in the exemption list", async() => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor4, true, {from: token_owner}); + 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); + let diff = beforeBal.minus(afterBal); + assert.equal(web3.utils.fromWei((diff.toNumber()).toString()), 3); + }); + }); + + describe("Test for modify functions", async() => { + + it("Should add the individual restriction for multiple investor", async() => { + await I_VolumeRestrictionTM.addIndividualRestrictionMulti( + [account_investor3, account_delegate3], + [web3.utils.toWei("15"), new BigNumber(12.78).times(new BigNumber(10).pow(16))], + [latestTime() + duration.days(1), latestTime() + duration.days(2)], + [15, 20], + [latestTime() + duration.days(40), latestTime() + duration.days(60)], + [0,1], + { + from: token_owner + } + ); + + let indi1 = await I_VolumeRestrictionTM.individualRestriction.call(account_investor3); + let indi2 = await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3); + + assert.equal(indi1[0].dividedBy(new BigNumber(10).pow(18)), 15); + assert.equal(indi2[0].dividedBy(new BigNumber(10).pow(16)), 12.78); + + assert.equal(indi1[2].toNumber(), 15); + assert.equal(indi2[2].toNumber(), 20); + + assert.equal(indi1[4].toNumber(), 0); + assert.equal(indi2[4].toNumber(), 1); + }); + + it("Should modify the details before the starttime passed", async() => { + await I_VolumeRestrictionTM.modifyIndividualRestrictionMulti( + [account_investor3, account_delegate3], + [new BigNumber(12.78).times(new BigNumber(10).pow(16)), web3.utils.toWei("15")], + [latestTime() + duration.days(1), latestTime() + duration.days(2)], + [20, 15], + [latestTime() + duration.days(40), latestTime() + duration.days(60)], + [1,0], + { + from: token_owner + } + ); + + let indi1 = await I_VolumeRestrictionTM.individualRestriction.call(account_investor3); + let indi2 = await I_VolumeRestrictionTM.individualRestriction.call(account_delegate3); + + assert.equal(indi2[0].dividedBy(new BigNumber(10).pow(18)), 15); + assert.equal(indi1[0].dividedBy(new BigNumber(10).pow(16)), 12.78); + + assert.equal(indi2[2].toNumber(), 15); + assert.equal(indi1[2].toNumber(), 20); + + assert.equal(indi2[4].toNumber(), 0); + assert.equal(indi1[4].toNumber(), 1); + }); + + }); + + describe("VolumeRestriction Transfer Manager Factory test cases", async() => { + + it("Should get the exact details of the factory", async() => { + assert.equal(await I_VolumeRestrictionTMFactory.getSetupCost.call(),0); + assert.equal((await I_VolumeRestrictionTMFactory.getTypes.call())[0],2); + assert.equal(web3.utils.toAscii(await I_VolumeRestrictionTMFactory.getName.call()) + .replace(/\u0000/g, ''), + "VolumeRestrictionTM", + "Wrong Module added"); + assert.equal(await I_VolumeRestrictionTMFactory.description.call(), + "Manage transfers based on the volume of tokens that needs to be transact", + "Wrong Module added"); + assert.equal(await I_VolumeRestrictionTMFactory.title.call(), + "Volume Restriction Transfer Manager", + "Wrong Module added"); + assert.equal(await I_VolumeRestrictionTMFactory.getInstructions.call(), + "Module used to restrict the volume of tokens traded by the token holders", + "Wrong Module added"); + assert.equal(await I_VolumeRestrictionTMFactory.version.call(), "1.0.0"); + }); + + it("Should get the tags of the factory", async() => { + let tags = await I_VolumeRestrictionTMFactory.getTags.call(); + assert.equal(tags.length, 5); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Maximum Volume"); + }); + }); + +}); \ No newline at end of file diff --git a/test/z_fuzzer_volumn_restriction_transfer_manager.js b/test/z_fuzzer_volumn_restriction_transfer_manager.js new file mode 100644 index 000000000..b03ab6fdc --- /dev/null +++ b/test/z_fuzzer_volumn_restriction_transfer_manager.js @@ -0,0 +1,711 @@ +import latestTime from './helpers/latestTime'; +import {signData} from './helpers/signData'; +import { pk } from './helpers/testprivateKey'; +import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; +import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; +import { catchRevert } from "./helpers/exceptions"; +import { setUpPolymathNetwork, deployVRTMAndVerifyed } from "./helpers/createInstances"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager.sol'); +const VolumeRestrictionTM = artifacts.require('./VolumeRestrictionTM.sol'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('VolumeRestrictionTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let token_owner_pk; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + let account_delegate; + let account_delegate2; + let account_delegate3; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_VolumeRestrictionTMFactory; + let P_VolumeRestrictionTMFactory; + let I_SecurityTokenRegistryProxy; + let P_VolumeRestrictionTM; + let I_GeneralTransferManagerFactory; + let I_VolumeRestrictionTM; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_DummySTOFactory; + let I_STFactory; + let I_SecurityToken; + let I_MRProxied; + let I_STRProxied; + 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"; + const delegateDetails = "Hello I am legit delegate"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + let tempAmount = new BigNumber(0); + let tempArray = new Array(); + let tempArray3 = new Array(); + let tempArrayGlobal = new Array(); + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + async function print(data, account) { + console.log(` + Latest timestamp: ${data[0].toNumber()} + SumOfLastPeriod: ${data[1].dividedBy(new BigNumber(10).pow(18)).toNumber()} + Days Covered: ${data[2].toNumber()} + Latest timestamp daily: ${data[3].toNumber()} + Individual Total Trade on latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[0])) + .dividedBy(new BigNumber(10).pow(18)).toNumber()} + Individual Total Trade on daily latestTimestamp : ${(await I_VolumeRestrictionTM.getTotalTradedByUser.call(account, data[3])) + .dividedBy(new BigNumber(10).pow(18)).toNumber()} + `) + } + + async function calculateSum(rollingPeriod, tempArray) { + let sum = 0; + let start = 0; + if (tempArray.length >= rollingPeriod) + start = tempArray.length - rollingPeriod; + for (let i = start; i < tempArray.length; i++) { + sum += tempArray[i]; + } + return sum; + } + + async function printIR(data) { + console.log(` + Allowed Tokens : ${data[0].dividedBy(new BigNumber(10).pow(18)).toNumber()} + StartTime : ${data[1].toNumber()} + Rolling Period : ${data[2].toNumber()} + EndTime : ${data[3].toNumber()} + Restriction Type: ${data[4].toNumber() == 0 ? "Fixed" : "Percentage"} + `) + } + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + token_owner_pk = pk.account_1; + + account_investor1 = accounts[8]; + account_investor2 = accounts[9]; + account_investor3 = accounts[4]; + account_investor4 = accounts[3]; + account_delegate = accounts[7]; + account_delegate2 = accounts[6]; + account_delegate3 = accounts[5]; + + // Step 1: Deploy the genral PM ecosystem + 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 5: Deploy the VolumeRestrictionTMFactory + [I_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 6: Deploy the VolumeRestrictionTMFactory + [P_VolumeRestrictionTMFactory] = await deployVRTMAndVerifyed(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} + ModuleRegistryProxy ${I_ModuleRegistryProxy.address} + ModuleRegistry: ${I_ModuleRegistry.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + VolumeRestrictionTMFactory: ${I_VolumeRestrictionTMFactory.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, true, { 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("Attach the VRTMaaaaa", async() => { + it("Deploy the VRTM and attach with the ST", async()=> { + let tx = await I_SecurityToken.addModule(I_VolumeRestrictionTMFactory.address, 0, 0, 0, {from: token_owner }); + assert.equal(tx.logs[2].args._moduleFactory, I_VolumeRestrictionTMFactory.address); + assert.equal( + web3.utils.toUtf8(tx.logs[2].args._name), + "VolumeRestrictionTM", + "VolumeRestrictionTMFactory doesn not added"); + I_VolumeRestrictionTM = VolumeRestrictionTM.at(tx.logs[2].args._module); + }); + + it("Transfer some tokens to different account", async() => { + // Add tokens in to the whitelist + await I_GeneralTransferManager.modifyWhitelistMulti( + [account_investor1, account_investor2, account_investor3], + [latestTime(), latestTime(), latestTime()], + [latestTime(), latestTime(), latestTime()], + [latestTime() + duration.days(60), latestTime() + duration.days(60), latestTime() + duration.days(60)], + [true, true, true], + { + from: token_owner + } + ); + + // Mint some tokens and transferred to whitelisted addresses + await I_SecurityToken.mint(account_investor1, web3.utils.toWei("100", "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}); + + }); + + }); + + describe("Fuzz test", async () => { + + it("Should work with multiple transaction within 1 day with Individual and daily Restrictions", async() => { + // let snapId = await takeSnapshot(); + + var testRepeat = 0; + + for (var i = 0; i < testRepeat; i++) { + + console.log("fuzzer number " + i); + + var individualRestrictTotalAmount = Math.floor(Math.random() * 10); + if ( individualRestrictTotalAmount == 0 ) { + individualRestrictTotalAmount = 1; + } + + var dailyRestrictionAmount = Math.floor(Math.random() * 10); + if ( dailyRestrictionAmount == 0 ) { + dailyRestrictionAmount = 1; + } + var rollingPeriod = 2; + var sumOfLastPeriod = 0; + + console.log("a"); + + // 1 - add individual restriction with a random number + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei(individualRestrictTotalAmount.toString()), + latestTime() + duration.seconds(2), + rollingPeriod, + latestTime() + duration.days(3), + 0, + { + from: token_owner + } + ); + + console.log("b"); + tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor1, + web3.utils.toWei(dailyRestrictionAmount.toString()), + latestTime() + duration.seconds(1), + latestTime() + duration.days(4), + 0, + { + from: token_owner + } + ); + + console.log("c"); + var txNumber = 10; // define fuzz test amount for tx within 24 hrs + + for (var j=0; j individualRestrictTotalAmount || accumulatedTxValue > dailyRestrictionAmount) { + console.log("tx should fail"); + + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}) + ); + + console.log("tx failed as expected due to over limit"); + + } else if (accumulatedTxValue <= individualRestrictTotalAmount && accumulatedTxValue <= dailyRestrictionAmount) { + console.log("tx should succeed"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + sumOfLastPeriod = sumOfLastPeriod + transactionAmount; + + console.log("tx succeeded"); + } + console.log("2"); + } + + // await revertToSnapshot(snapId); + await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor1, {from: token_owner}); + await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor1, {from: token_owner}); + } + }); + + it("Should work with fuzz test for individual restriction and general restriction", async() => { + // let snapId = await takeSnapshot(); + var testRepeat = 0; + + for (var i = 0; i < testRepeat; i++) { + + console.log("fuzzer number " + i); + + var individualRestrictTotalAmount = Math.floor(Math.random() * 10); + if (individualRestrictTotalAmount == 0 ) { + individualRestrictTotalAmount = 1; + } + var defaultRestrictionAmount = Math.floor(Math.random() * 10); + var rollingPeriod = 2; + var sumOfLastPeriod = 0; + + console.log("a"); + + // 1 - add individual restriction with a random number + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei(individualRestrictTotalAmount.toString()), + latestTime() + duration.seconds(1), + rollingPeriod, + latestTime() + duration.days(3), + 0, + { + from: token_owner + } + ); + + console.log("b"); + tx = await I_VolumeRestrictionTM.addDefaultRestriction( + account_investor1, + latestTime() + duration.seconds(1), + rollingPeriod, + latestTime() + duration.days(4), + 0, + { + from: token_owner + } + ); + + console.log("c"); + var txNumber = 10; // define fuzz test amount for tx + + for (var j = 0; j < txNumber; j++) { + await increaseTime(duration.seconds(5)); + console.log("2"); + + // generate a random amount + var transactionAmount = Math.floor(Math.random() * 10); + var accumulatedTxValue = transactionAmount + sumOfLastPeriod; + + console.log("sumOfLastPeriod is " + sumOfLastPeriod + " transactionAmount is " + transactionAmount + " individualRestrictTotalAmount is " + individualRestrictTotalAmount + " defaultRestrictionAmount is " + defaultRestrictionAmount); + + + // check against daily and total restrictions to determine if the transaction should pass or not + if (accumulatedTxValue > individualRestrictTotalAmount || accumulatedTxValue > defaultRestrictionAmount) { + console.log("tx should fail"); + + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}) + ); + + console.log("tx failed as expected due to over limit"); + } else if (accumulatedTxValue <= individualRestrictTotalAmount) { + + console.log("tx should succeed"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + sumOfLastPeriod = sumOfLastPeriod + transactionAmount; + + console.log("tx succeeded"); + } + console.log("3"); + }; + + + // remove individual restriction and it should fall to default restriction + await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor1, {from: token_owner}); + console.log("individual restriction now removed --> fall back to default restriction"); + + for (var j=0; j defaultRestrictionAmount) { + console.log("tx should fail"); + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}) + ); + console.log("tx failed as expected due to over limit"); + } else if ( accumulatedTxValue <= defaultRestrictionAmount ) { + + console.log("tx should succeed"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + sumOfLastPeriod = sumOfLastPeriod + transactionAmount; + + console.log("tx succeeded"); + } + console.log("5"); + } + + + // await revertToSnapshot(snapId); + await I_VolumeRestrictionTM.removeDefaultRestriction(account_investor1, {from: token_owner}); + } + + }); + + + + it("Should work with fuzz test for randomly adding / removing individual daily restriction and perform multipel transactions", async() => { + + + var testRepeat = 0; + var txNumber = 10; + var dailyRestriction = false; + var startTime = 1; + var sumOfLastPeriod = 0; + var accumulatedTimeIncrease = 0; + var dailyLimitUsed = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + + for (var i = 0; i < testRepeat; i++) { + + // randomly add or removing existing daily restriction + var random_action = Math.random() >= 0.5; // true -> add false -> remove + + if (dailyRestriction === false && random_action === true) { + console.log("1"); + + var dailyRestrictionAmount = Math.floor(Math.random() * 10); + if (dailyRestrictionAmount === 0) { + dailyRestrictionAmount = 1; + } + + // add daily restriction + let tx = await I_VolumeRestrictionTM.addIndividualDailyRestriction( + account_investor1, + web3.utils.toWei(dailyRestrictionAmount.toString()), + latestTime() + duration.seconds(startTime), + latestTime() + duration.days(50), + 0, + { + from: token_owner + } + ); + + dailyRestriction = true; + + console.log("added daily restriction"); + } else if (dailyRestriction === true && random_action === false) { + console.log("2"); + // remove daily restriction + await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor1, {from: token_owner}); + console.log("removed daily restriction"); + + dailyRestriction = false; + } + + // perform multiple transactions + + for (var j = 0; j < txNumber; j++) { + var timeIncreaseBetweenTx = Math.floor(Math.random() * 10) * 3600; + + await increaseTime(duration.seconds(timeIncreaseBetweenTx)); + accumulatedTimeIncrease = timeIncreaseBetweenTx + accumulatedTimeIncrease; + console.log("4"); + + // generate a random amount + var transactionAmount = Math.floor(Math.random() * 10); + + // check today's limit + var dayNumber = Math.floor(accumulatedTimeIncrease/(24*3600)) + 1; + + var todayLimitUsed = dailyLimitUsed[dayNumber]; + + console.log("todayLimitUsed is " + todayLimitUsed + " transactionAmount is " + transactionAmount + " dayNumber is " + dayNumber + " dailyRestrictionAmount is " + dailyRestrictionAmount); + + // check against daily and total restrictions to determine if the transaction should pass or not + if ((todayLimitUsed + transactionAmount) > dailyRestrictionAmount) { + console.log("tx should fail"); + + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}) + ); + + console.log("tx failed as expected due to over limit"); + } else if ((todayLimitUsed + transactionAmount) <= dailyRestrictionAmount) { + + console.log("tx should succeed"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + dailyLimitUsed[dayNumber] = dailyLimitUsed[dayNumber] + transactionAmount; + + console.log("tx succeeded"); + } + console.log("5"); + } + + if (dailyRestriction === true) { + + // remove daily restriction + await I_VolumeRestrictionTM.removeIndividualDailyRestriction(account_investor1, {from: token_owner}); + console.log("removed daily restriction"); + } + + } + + }); + + + + + it("should work in all cases if a sender is added in the exception list", async () => { + var testRepeat = 0; + + for (var i = 0; i < testRepeat; i++) { + console.log("fuzzer number " + i); + + var individualRestrictTotalAmount = Math.floor(Math.random() * 10); + if (individualRestrictTotalAmount === 0 ) { + individualRestrictTotalAmount = 1; + } + var defaultRestrictionAmount = Math.floor(Math.random() * 10); + var rollingPeriod = 2; + var sumOfLastPeriod = 0; + + console.log("a"); + + // 1 - add individual restriction with a random number + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei(individualRestrictTotalAmount.toString()), + latestTime() + duration.seconds(1), + rollingPeriod, + latestTime() + duration.days(3), + 0, + { + from: token_owner + } + ); + + tx = await I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, true, {from: token_owner}); + + console.log("b"); + + var txNumber = 10; // define fuzz test amount for tx + + for (var j = 0; j < txNumber; j++) { + await increaseTime(duration.seconds(5)); + console.log("2"); + + // generate a random amount + var transactionAmount = Math.floor(Math.random() * 10); + var accumulatedTxValue = transactionAmount + sumOfLastPeriod; + + console.log("sumOfLastPeriod is " + sumOfLastPeriod + " transactionAmount is " + transactionAmount + " individualRestrictTotalAmount is " + individualRestrictTotalAmount + " defaultRestrictionAmount is " + defaultRestrictionAmount); + + // check against daily and total restrictions to determine if the transaction should pass or not + if (accumulatedTxValue > individualRestrictTotalAmount) { + console.log("tx should fail but still succeed due to investor in exempt list"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + console.log("tx passed as expected"); + } else if (accumulatedTxValue <= individualRestrictTotalAmount) { + + console.log("tx should succeed"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + sumOfLastPeriod = sumOfLastPeriod + transactionAmount; + + console.log("tx succeeded"); + } + console.log("3" + txNumber); + }; + + await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor1, {from: token_owner}); + console.log("removed daily restriction"); + } + }); + + it("Should work if IR is modified", async () => { + + console.log(`\t\t Starting of the IR modification test case`.blue); + + var testRepeat = 1; + + for (var i = 0; i < testRepeat; i++) { + console.log("\t\t fuzzer number " + i); + let precision = 100; + var individualRestrictionTotalAmount = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + var rollingPeriod = 2; + var sumOfLastPeriod = 0; + + console.log(`\t\t Add individual restriction with TotalAmount: ${individualRestrictionTotalAmount}\n`.green); + + // 1 - add individual restriction with a random number + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei(individualRestrictionTotalAmount.toString()), + latestTime() + duration.days(2), + rollingPeriod, + latestTime() + duration.days(5), + 0, + { + from: token_owner + } + ); + + console.log(`\t\t Restriction successfully added \n`); + + var txNumber = 10; // define fuzz test amount for tx + + for (var j = 0; j < txNumber; j++) { + console.log(`\t\t Test number: ${j}\n`); + + // modify IR + var newIR = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + + printIR(await I_VolumeRestrictionTM.individualRestriction(account_investor1, {from: token_owner})); + + console.log(`\t\t Modification of the IR with new startTime: ${latestTime() + duration.days(1+j)} and new total amount: ${newIR} `.green); + + await I_VolumeRestrictionTM.modifyIndividualRestriction( + account_investor1, + web3.utils.toWei(newIR.toString()), + latestTime() + duration.days(1+j), + rollingPeriod, + latestTime() + duration.days(5+j), + 0, + { from: token_owner } + ); + + console.log(`\t\t Successfully IR modified`); + let snapId = await takeSnapshot(); + await increaseTime(duration.days(2+j)); + + // generate a random amount + var transactionAmount = Math.floor(Math.random() * (10 * precision - 1 * precision) + 1 * precision) / (1*precision); + + // check against daily and total restrictions to determine if the transaction should pass or not + if (transactionAmount > newIR) { + console.log("\t\t Tx should fail"); + + await catchRevert ( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}) + ); + + console.log("\t\t Tx failed as expected"); + } else if (transactionAmount <= newIR) { + + console.log("\t\t Tx should succeed"); + + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei(transactionAmount.toString()), {from: account_investor1}); + + console.log("\t\t Tx succeeded"); + } + await revertToSnapshot(snapId); + console.log("\t\t Finished test number "+j); + }; + + await I_VolumeRestrictionTM.removeIndividualRestriction(account_investor1, {from: token_owner}); + console.log("\t\t Removed daily restriction"); + } + + }); + + }); + +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index a8a3aa60d..39e3b7bb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -61,9 +61,9 @@ acorn-jsx@^5.0.0: integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== acorn@^6.0.2: - version "6.0.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754" - integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg== + version "6.0.5" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.5.tgz#81730c0815f3f3b34d8efa95cb7430965f4d887a" + integrity sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg== aes-js@3.0.0: version "3.0.0" @@ -253,7 +253,7 @@ async-eventemitter@^0.2.2: dependencies: async "^2.4.0" -async-eventemitter@ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c: +"async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c": version "0.2.3" resolved "https://codeload.github.com/ahultgren/async-eventemitter/tar.gz/fa06e39e56786ba541c180061dbf2c0a5bbf951c" dependencies: @@ -1019,19 +1019,19 @@ bignumber.js@^4.0.2: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1" integrity sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA== -bignumber.js@^7.2.1: - version "7.2.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" - integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== - -"bignumber.js@git+https://github.com/debris/bignumber.js#master": - version "2.0.7" - resolved "git+https://github.com/debris/bignumber.js#c7a38de919ed75e6fb6ba38051986e294b328df9" +bignumber.js@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.1.tgz#5d419191370fb558c64e3e5f70d68e5947138832" + integrity sha512-zAySveTJXkgLYCBi0b14xzfnOs+f3G6x36I8w2a1+PFQpWk/dp0mI0F+ZZK2bu+3ELewDcSyP+Cfq++NcHX7sg== "bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2": version "2.0.7" resolved "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" +"bignumber.js@git+https://github.com/debris/bignumber.js.git#master": + version "2.0.7" + resolved "git+https://github.com/debris/bignumber.js.git#c7a38de919ed75e6fb6ba38051986e294b328df9" + "bignumber.js@git+https://github.com/frozeman/bignumber.js-nolookahead.git": version "2.0.7" resolved "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" @@ -1142,14 +1142,15 @@ body-parser@1.18.3, body-parser@^1.16.0: type-is "~1.6.16" borc@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/borc/-/borc-2.0.4.tgz#52926dc561137188c6ca9fe01c9542576529a689" - integrity sha512-SCVjto/dbKfduyl+LDQ1Km28ly2aTIXtJbrYZWHFQAxkHph96I/zXTrTQXWuJobG8lQZjIA/dw9z7hmJHJhjMg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.0.tgz#2def2fc69868633b965a9750e7f210d778190303" + integrity sha512-hKTxeYt3AIzIG45epJHv8xJYSF0ktp7nZgFsqi5cPzoL3T8qKMPeUlqydORy6j3NWZvRDANx30PjpTmGho69Gw== dependencies: - bignumber.js "^7.2.1" + bignumber.js "^8.0.1" commander "^2.15.0" ieee754 "^1.1.8" - json-text-sequence "^0.1" + iso-url "~0.4.4" + json-text-sequence "~0.1.0" brace-expansion@^1.1.7: version "1.1.11" @@ -1418,9 +1419,9 @@ caminte@0.3.7: uuid "^3.0.1" caniuse-lite@^1.0.30000844: - version "1.0.30000923" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000923.tgz#148f9bda508024b5ce957b463ae2e8302b451bb2" - integrity sha512-j5ur7eeluOFjjPUkydtXP4KFAsmH3XaQNch5tvWSO+dLHYt5PE+VgJZLWtbVOodfWij6m6zas28T4gB/cLYq1w== + version "1.0.30000926" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000926.tgz#4361a99d818ca6e521dbe89a732de62a194a789c" + integrity sha512-diMkEvxfFw09SkbErCLmw/1Fx1ZZe9xfWm4aeA2PUffB48x1tfZeMsK5j4BW7zN7Y4PdqmPVVdG2eYjE5IRTag== caseless@~0.12.0: version "0.12.0" @@ -1826,9 +1827,9 @@ csextends@^1.0.3: integrity sha512-S/8k1bDTJIwuGgQYmsRoE+8P+ohV32WhQ0l4zqrc0XDdxOhjQQD7/wTZwCzoZX53jSX3V/qwjT+OkPTxWQcmjg== csv-parse@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.2.0.tgz#6f3c09c5a46c06f19ca642a7b3104908d45b048c" - integrity sha512-t8KGN+ZKIYUUqXznAIZ723KI3IBILImjhN8VUfAod/VCtn/IpW3R24PYCxl81g4/z4R9DruTVyvavyiw2VRLgw== + version "4.3.0" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.3.0.tgz#e27cc1c4c5426201a896389c9f97273a0d47dad4" + integrity sha512-vrZTSMG/6a5EI30ICFU8IQ1kR3xXXoC3Z7jaUv76E5uCgFahr2sffRoTAffvEd1JZNssAgalI4DaEFUnXqQCGw== cycle@1.0.x: version "1.0.3" @@ -1855,9 +1856,9 @@ death@^1.1.0: integrity sha1-AaqcQB7dknUFFEcLgmY5DGbGcxg= debug@*, debug@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" - integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" @@ -2063,7 +2064,7 @@ diff@3.3.1: resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" integrity sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww== -diff@3.5.0: +diff@3.5.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -2126,17 +2127,17 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -editions@^1.1.1, editions@^1.3.3, editions@^1.3.4: +editions@^1.1.1, editions@^1.3.3: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" integrity sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg== -editions@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/editions/-/editions-2.1.0.tgz#5c6f6341ef19ee362a3bcbb907fe68e696dbc69e" - integrity sha512-yKrimWcvOXcYXtqsOeebbMLynm9qbYVd0005wveGU2biPxJaJoxA0jtaZrxiMe3mAanLr5lxoYFVz5zjv9JdnA== +editions@^2.1.0, editions@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/editions/-/editions-2.1.3.tgz#727ccf3ec2c7b12dcc652c71000f16c4824d6f7d" + integrity sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw== dependencies: - errlop "^1.0.3" + errlop "^1.1.1" semver "^5.6.0" ee-first@1.1.1: @@ -2145,9 +2146,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.47: - version "1.3.95" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.95.tgz#79fac438813ca7f3db182a525c2ab432934f6484" - integrity sha512-0JZEDKOQAE05EO/4rk3vLAE+PYFI9OLCVLAS4QAq1y+Bb2y1N6MyQJz62ynzHN/y0Ka/nO5jVJcahbCEdfiXLQ== + version "1.3.96" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.96.tgz#25770ec99b8b07706dedf3a5f43fa50cb54c4f9a" + integrity sha512-ZUXBUyGLeoJxp4Nt6G/GjBRLnyz8IKQGexZ2ndWaoegThgMGFO1tdDYID5gBV32/1S83osjJHyfzvanE/8HY4Q== elliptic@6.3.3: version "6.3.3" @@ -2219,12 +2220,12 @@ eol@^0.9.1: resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" integrity sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg== -errlop@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.0.3.tgz#dba29c90cf832c3d2ce469fe515d7e5eef2c6676" - integrity sha512-5VTnt0yikY4LlQEfCXVSqfE6oLj1HVM4zVSvAKMnoYjL/zrb6nqiLowZS4XlG7xENfyj7lpYWvT+wfSCr6dtlA== +errlop@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.1.1.tgz#d9ae4c76c3e64956c5d79e6e035d6343bfd62250" + integrity sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw== dependencies: - editions "^1.3.4" + editions "^2.1.2" errno@~0.1.1: version "0.1.7" @@ -2241,17 +2242,18 @@ error-ex@^1.2.0: is-arrayish "^0.2.1" es-abstract@^1.5.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" - integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== dependencies: - es-to-primitive "^1.1.1" + es-to-primitive "^1.2.0" function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" + has "^1.0.3" + is-callable "^1.1.4" is-regex "^1.0.4" + object-keys "^1.0.12" -es-to-primitive@^1.1.1: +es-to-primitive@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== @@ -2404,9 +2406,9 @@ eslint-visitor-keys@^1.0.0: integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== eslint@^5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.10.0.tgz#24adcbe92bf5eb1fc2d2f2b1eebe0c5e0713903a" - integrity sha512-HpqzC+BHULKlnPwWae9MaVZ5AXJKpkxCVXQHrFaRw3hbDj26V/9ArYM4Rr/SQ8pi6qUPLXSSXC4RBJlyq2Z2OQ== + version "5.11.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.11.1.tgz#8deda83db9f354bf9d3f53f9677af7e0e13eadda" + integrity sha512-gOKhM8JwlFOc2acbOrkYR05NW8M6DCMSvfcJiBB5NDxRE1gv8kbvxKaC9u69e6ZGEMWXcswA/7eKR229cEIpvg== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.5.3" @@ -3321,7 +3323,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@~7.1.2: +glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@~7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -3501,7 +3503,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1, has@~1.0.3: +has@^1.0.1, has@^1.0.3, has@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -3711,9 +3713,9 @@ inquirer@^6.1.0: through "^2.3.6" interpret@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - integrity sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ= + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== invariant@^2.2.2: version "2.2.4" @@ -3999,6 +4001,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +iso-url@~0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.4.tgz#473a45569b6015da0c23f831010aeff69e3841e8" + integrity sha512-mR7/oy5PRtuWaynQ+QRNGbGNg7j3VLIc99DvrSfjmretWTS5zwC3p/D5iX7UvtFU1dBB501wibJIwUuNeo3Rfw== + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -4156,7 +4163,7 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json-text-sequence@^0.1: +json-text-sequence@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.1.1.tgz#a72f217dc4afc4629fff5feb304dc1bd51a2f3d2" integrity sha1-py8hfcSvxGKf/1/rME3BvVGi89I= @@ -4779,11 +4786,16 @@ mustache@^2.3.0: resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" integrity sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ== -mute-stream@0.0.7, mute-stream@~0.0.4: +mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + mz@^2.6.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -5861,11 +5873,11 @@ revalidator@0.1.x: integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs= rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.6.1, rimraf@~2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: - glob "^7.0.5" + glob "^7.1.3" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" @@ -6001,9 +6013,9 @@ scryptsy@^1.2.1: pbkdf2 "^3.0.3" secp256k1@^3.0.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.5.2.tgz#f95f952057310722184fe9c914e6b71281f2f2ae" - integrity sha512-iin3kojdybY6NArd+UFsoTuapOF7bnJNf2UbcWXaY3z+E1sJDipl60vtzB5hbO/uquBu7z0fd4VC4Irp+xoFVQ== + version "3.6.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.6.1.tgz#f0475d42096218ff00e45a127242abdff9285335" + integrity sha512-utLpWv4P4agEw7hakR73wlWX0NBmC5t/vkJ0TAfTyvETAUzo0tm6aFKPYetVYRaVubxMeWm5Ekv9ETwOgcDCqw== dependencies: bindings "^1.2.1" bip66 "^1.1.3" @@ -6322,27 +6334,28 @@ solium-plugin-security@0.1.1: integrity sha512-kpLirBwIq4mhxk0Y/nn5cQ6qdJTI+U1LO3gpoNIcqNaW+sI058moXBe2UiHs+9wvF9IzYD49jcKhFTxcR9u9SQ== solium@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/solium/-/solium-1.1.8.tgz#35d30a15c572a233ce8a90226d6cfccb762fadb7" - integrity sha512-fn0lusM6of14CytIDDHK73SGjn6NsVTaCVJjaKCKJyqKhT00rH/hDtvnIeZ2ZTD9z/xaXd4Js2brW3az6AV9RA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/solium/-/solium-1.2.1.tgz#ead11f2921ba2337461752155cc96149ea08f247" + integrity sha512-PnNOx3BfYvghJE5aV2lWBcbNIa9g9eXQaGPpQPiYixmF7kVR27J2tmSetdsdpwKMVf2Y+H4JEEL7zyfsRjOMFA== dependencies: ajv "^5.2.2" chokidar "^1.6.0" colors "^1.1.2" commander "^2.9.0" + diff "^3.5.0" eol "^0.9.1" js-string-escape "^1.0.1" lodash "^4.14.2" sol-digger "0.0.2" sol-explore "1.6.1" solium-plugin-security "0.1.1" - solparse "2.2.5" + solparse "2.2.7" text-table "^0.2.0" -solparse@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/solparse/-/solparse-2.2.5.tgz#72709c867cd6bfc50ec2325f4b81d2b3ea365d99" - integrity sha512-t7tvtR6KU6QfPYLMv1nlCh9DA8HYIu5tbjHpKu0fhGFZ1NuSp0KKDHfFHv07g6v1xgcuUY3rVqNFjZt5b9+5qA== +solparse@2.2.7: + version "2.2.7" + resolved "https://registry.yarnpkg.com/solparse/-/solparse-2.2.7.tgz#5479ff4ba4ca6900df89b902b5af1ee23b816250" + integrity sha512-s2DKdvVxXcWefPgBSb+HgTylomzbNjsKdZDN6PsWi9h+UYdyw7DFozpPCGETNEz523tIjrqBRnSPIoDVL1zv5Q== dependencies: mocha "^4.0.1" pegjs "^0.10.0" @@ -6423,9 +6436,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz#a59efc09784c2a5bada13cfeaf5c75dd214044d2" - integrity sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg== + version "3.0.3" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" + integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -6445,9 +6458,9 @@ sprintf-js@~1.0.2: integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sshpk@^1.7.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" - integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== + version "1.16.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" + integrity sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -6655,9 +6668,9 @@ table@^5.0.2: string-width "^2.1.1" tape@^4.4.0, tape@^4.6.3: - version "4.9.1" - resolved "https://registry.yarnpkg.com/tape/-/tape-4.9.1.tgz#1173d7337e040c76fbf42ec86fcabedc9b3805c9" - integrity sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw== + version "4.9.2" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.9.2.tgz#f233e40f09dc7e00fcf9b26755453c3822ad28c0" + integrity sha512-lPXKRKILZ1kZaUy5ynWKs8ATGSUO7HAFHCFnBam6FaGSqPdOwMWbxXHq4EXFLE8WRTleo/YOMXkaUTRmTB1Fiw== dependencies: deep-equal "~1.0.1" defined "~1.0.0"