diff --git a/.circleci/config.yml b/.circleci/config.yml index 90e4fb7df..da2111136 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -39,17 +39,17 @@ jobs: path: ./test-results/mocha/results.xml coverage: docker: - - image: circleci/node:8 + - image: maxsam4/solidity-kit:0.4.24 steps: - checkout - restore_cache: key: dependency-cache-{{ checksum "package.json" }} - run: yarn install - - run: sudo npm i truffle -g - run: node --version - run: truffle version + - run: node_modules/.bin/truffle version - run: - command: npm run coverage + command: scripts/coverage.sh no_output_timeout: 1h - save_cache: key: dependency-cache-{{ checksum "package.json" }} @@ -57,7 +57,7 @@ jobs: - node_modules - store_artifacts: path: ./coverage/lcov.info - docs: + deploy_kovan: docker: - image: maxsam4/solidity-kit:0.4.24 steps: @@ -65,11 +65,25 @@ jobs: - restore_cache: key: dependency-cache-{{ checksum "package.json" }} - run: yarn install - - run: wget -O node_modules/solidity-docgen/lib/index.js https://raw.githubusercontent.com/maxsam4/solidity-docgen/build/lib/index.js - run: node --version - run: truffle version - - run: git config --global user.email "contact@mudit.blog" - - run: git config --global user.name "Docs Bot" + - run: mv truffle-ci.js truffle-config.js + - run: npm run deploy-kovan + - save_cache: + key: dependency-cache-{{ checksum "package.json" }} + paths: + - node_modules + docs: + docker: + - image: circleci/node:8 + steps: + - checkout + - restore_cache: + key: dependency-cache-{{ checksum "package.json" }} + - run: yarn install + - run: sudo npm i truffle -g + - run: node --version + - run: truffle version - run: npm run docs - save_cache: key: dependency-cache-{{ checksum "package.json" }} @@ -100,3 +114,13 @@ workflows: branches: only: - master + deploy: + jobs: + - deploy_kovan: + filters: + branches: + only: + - master + - dev-2.1.0 + - dev-2.2.0 + - dev-3.0.0 diff --git a/.eslintrc.js b/.eslintrc.js index 463539deb..8b0c0ac0f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,8 @@ module.exports = { "quotes": 0, "semi": 0, "no-undef": 0, - "key-spacing": 0 + "key-spacing": 0, + "no-tabs": 0, + "no-mixed-spaces-and-tabs":0 } }; diff --git a/.gitignore b/.gitignore index f1aed51ad..dac654947 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ allFiredEvents extract/ extract.py extract.zip -/test-results \ No newline at end of file +/test-results diff --git a/.solcover.js b/.solcover.js index 23ac4d3ef..dac86d6e3 100644 --- a/.solcover.js +++ b/.solcover.js @@ -2,8 +2,8 @@ module.exports = { norpc: true, port: 8545, copyPackages: ['openzeppelin-solidity'], - testCommand: 'node ../node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js` --network coverage', + testCommand: 'node ../node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js -and ! -name q_usd_tiered_sto_sim.js -and ! -name z_general_permission_manager_fuzzer.js` --network coverage', deepSkip: true, - skipFiles: ['external', 'flat', 'helpers', 'mocks', 'oracles', 'libraries/KindMath.sol', 'storage', 'modules/Experimental'], + skipFiles: ['external', 'flat', 'helpers', 'mocks', 'oracles', 'libraries/KindMath.sol', 'libraries/BokkyPooBahsDateTimeLibrary.sol', 'storage', 'modules/Experimental'], forceParse: ['mocks', 'oracles', 'modules/Experimental'] }; 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 9b1b3374f..836b840d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,87 @@ # Changelog All notable changes to this project will be documented in this file. -# v2.1.0 - Release Candidate - -[__2.1.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __13-089-18__ +# v2.1.0 - Release Candidate + +[__2.1.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __13-09-18__ + + +## CappedSTO 2.1.0 +* `rate` is now accepted as multiplied by 10^18 to allow settting higher price than 1ETH/POLY per token. +* Indivisble tokens are now supported. When trying to buy partial tokens, allowed full units of tokens will be purchased and remaining funds will be returned. + +## USDTieredSTO 2.1.0 +* Added `stableCoinsRaised` function that returns amount of individual stable coin raised when address of that stable coin is passed. +* Added support for multiple stable coins in USDTSTO. +* Added `buyTokensView` and `getTokensMintedByTier` to USDTSTO. +* Added `getSTODetails` to USDTSTO. +* Added an Array of Tiers that will hold data about every tier in USDTSTO. +* Added `buyWithETHRateLimited`, `buyWithPOLYRateLimited` and `buyWithUSDRateLimited` to USDTSTO. +* Added `getTokensSoldByTier` to return sold (not minted during finalisation) tokens in each tier to USDTSTO. +* Removed individual mappings for tier data removed in UDSTSTO. +* Removed the old Proxy deployment method of USDTieredSTO and adopt the new inherited proxy deployment approach. +* Bump the version to `2.1.0` +* Added `getAccreditedData` to return accredited & non-accredited investor data. +* Event `TokenPurchase` has uint256 tier instead of uint8 tier. +* Event `SetAddresses` has non-indexed array of address of `_usdTokens` rather than single indexed address. +* Added `getUsdTokens()` function that returns array of accepted stable coin (usd token) addresses. +* Pass an array of `_usdToken` address in `configure` function instead of singleton address. This will require changes in bytes data generation when deploying a usdtsto through factory. + +## GeneralTransferManager +* `getInvestors`, `getAllInvestorsData`, `getInvestorsData` added to GTM to allow easy data queries. +* `changeDefaults(uint64 _defaultFromTime, uint64 _defaultToTime)` added which sets a default timestamp used when `fromTime` or `toTime` are 0. +* Add `address[] public investors` to record a list of all addresses that have been added to the whitelist (`getInvestors`). +* Fix for when `allowAllWhitelistIssuances` is FALSE +* Make GTM a Proxy based implementation to reduce deployment gas costs +* Changed the version of `GeneralTransferManagerFactory` from `1.0.0` to `2.1.0`. +* `_investor` and `_addedBy` is now indexed in the `ModifyWhitelist` event. +* Add public variable `defaults` to get the offset timing. ## Manual Approval TransferManager -* Removed `0x0` check for the `_from` address to `ManualApprovalTransferManager`. This allows for the Issuer/Transfer Agent to approve a one-off mint of tokens that otherwise would not be possible. -* Changed the version of `ManualApprovalTransferManagerFactory` from `1.0.0` to `2.0.1`. +* Removed `0x0` check for the `_from` address to `ManualApprovalTransferManager`. This allows for the Issuer/Transfer Agent to approve a one-off mint of tokens that otherwise would not be possible. +* Changed the version of `ManualApprovalTransferManagerFactory` from `1.0.0` to `2.1.0`. * Deployed 2.0.1 `ManualApprovalTransferManagerFactory` to address 0x6af2afad53cb334e62b90ddbdcf3a086f654c298 +* Add `getActiveApprovalsToUser()` function to access all the active approvals for a user whether user is in the `from` or in `to`. +* Add `getApprovalDetails()` to get the details of the approval corresponds to `_from` and `_to` address. +* Add feature to modify the details of the active approval using `modifyApproval()` & `modifyApprovalMulti()`. +* Add `addManualApprovalMulti()` and `revokeManualApprovalMulti()` batch function for adding and revoking the manual approval respectively. +* Add `_description` parameter during the `addManualApproval()` function call. It will be a `bytes32` variable which depicts the cause of manual approval. +* Remove `addManualBlocking()` , `revokeManualBlocking()` functions. +* Add `getTotalApprovalsLength()` to get the number of active approvals. +* Add `getAllApprovals()` to get the details of all approvals. + +## Dividends +* Changed the version of `ERC20DividendCheckpointFactory` & `EtherDividendCheckpointFactory` from `1.0.0` to `2.1.0`. +* Applied proxy pattern to Dividends modules. +* During the launch of dividend module issuer need to pass the reclaimed wallet that receive the left over funds from the module. +i.e pass `_wallet` in `configure()` function of dividend module. It emits `SetWallet` event for the confirmation of the same. +* Add `changeWallet()` function to change the reclaimed wallet address (only be called by the owner). +* Add `getDividendsData()` getter to receive the details about all the dividend. +* Add `getDividendData()` getter to receive the details about the particular dividend by passing a corresponding dividend index. +* Add `getDividendProgress()` getter to retrieves the list of investors and their details corresponds to particular dividend. +* Add `getCheckpointData()` use to retrieves list of investors, their balances, and their current withholding tax percentage corresponds to checkpointId. +* `isExcluded()` a view function added to check whether an address is excluded from claming a dividend or not. +* `isClaimed()` a view function added to checks whether an address has claimed a dividend or not. +* DividendIndex is indexed in the events `ERC20DividendClaimed`, `ERC20DividendReclaimed`, `ERC20DividendWithholdingWithdrawn`. Similarly for the Ether dividend module `EtherDividendClaimed`, `EtherDividendReclaimed`, `EtherDividendClaimFailed`, `EtherDividendWithholdingWithdrawn`. +* `EXCLUDED_ADDRESS_LIMIT` changed from 50 to 150. + +## Experimental modules +* Remove the `SingleTradeVolumeRestrictionTMFactory.sol` and its corresponding module `SingleTradeVolumeRestrictionTM.sol`. +* Add the new TM called `BlacklistTransferManager.sol` and its corresponding factory `BlacklistTransferManagerFactory.sol`. +* Chnage the name of module from `LockupVolumeRestrictionTM.sol` to `LockUpTransferManager.sol`, similarly factory become `LockUpTransferManagerFactory.sol`. +* Add new module called `VestingEscrowWallet.sol` and its corresponding factory `VestingEscrowWalletFactory.sol`. + +## STR & MR +* `getArrayAddress(), getArrayBytes32(), getArrayUint()` are now public getters. +* `getUintValues(), getBoolValues(), getStringValues(), getAddressValues(), getBytes32Values(), getBytesValues()` rename to `getUintValue(), getBoolValue(), getStringValue(), getAddressValue(), getBytes32Value(), getBytesValue()`. #488 + +## Added +* Add new module called `VolumeRestrictionTM.sol` under the TransferManager modules list. It will be used to restrict the token +volume traded in a given rolling period. + +## Changed +* `getAllModulesAndPermsFromTypes()` does not take securityToken address as a parameter anymore. + # v1.5.0 - Release Candidate @@ -43,7 +116,6 @@ All notable changes to this project will be documented in this file. * Add new function `modifyTickerDetails()`, To modify the details of undeployed ticker. #230 ## Fixed -* `getAllModulesAndPermsFromTypes()` does not take securityToken address as a parameter anymore. * 0x0 and duplicate address in exclusions are no longer allowed in dividend modules. * All permissions are denied if no permission manager is active. * Generalize the STO varaible names and added them in `ISTO.sol` to use the common standard in all STOs. @@ -54,11 +126,11 @@ All notable changes to this project will be documented in this file. * Removed investors list pruning * Remove `swarmHash` from the `registerTicker(), addCustomTicker(), generateSecurityToken(), addCustomSecurityToken()` functions of TickerRegistry.sol and SecurityTokenRegistry.sol. #230 * Remove `Log` prefix from all the event present in the ecosystem. -* Removed `addTagByModuleType` & `removeTagsByModuleType` from MR. +* Removed `addTagByModuleType` & `removeTagsByModuleType` from MR. ====== -# v1.4.1 - Release Candidate +# v1.4.1 [__1.4.1__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __13-08-18__ @@ -82,7 +154,7 @@ All notable changes to this project will be documented in this file. * Fix #238: make beneficial investments optionally supported (default to not allowed) -# v1.4.0 - Release candidate +# v1.4.0 [__1.4.0__](https://www.npmjs.com/package/polymath-core?activeTab=readme) __13-08-18__ diff --git a/CLI/commands/ST20Generator.js b/CLI/commands/ST20Generator.js index e76fad960..2b9311172 100644 --- a/CLI/commands/ST20Generator.js +++ b/CLI/commands/ST20Generator.js @@ -1,60 +1,22 @@ -var readlineSync = require('readline-sync'); -var BigNumber = require('bignumber.js'); -var moment = require('moment'); -var chalk = require('chalk'); -const shell = require('shelljs'); -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis'); -var common = require('./common/common_functions'); -var global = require('./common/global'); +const readlineSync = require('readline-sync'); +const BigNumber = require('bignumber.js'); +const moment = require('moment'); +const chalk = require('chalk'); +const tokenManager = require('./token_manager'); +const contracts = require('./helpers/contract_addresses'); +const abis = require('./helpers/contract_abis'); +const common = require('./common/common_functions'); +//////////////////////// let securityTokenRegistryAddress; - -/////////////////// -// Crowdsale params -let tokenName; let tokenSymbol; -let selectedSTO; - -const MODULES_TYPES = { - PERMISSION: 1, - TRANSFER: 2, - STO: 3, - DIVIDENDS: 4, - BURN: 5 -} +let tokenLaunched; -const cappedSTOFee = 20000; -const usdTieredSTOFee = 100000; -const tokenDetails = ""; -const FUND_RAISE_TYPES = { - ETH: 0, - POLY: 1, - DAI: 2 -} -//////////////////////// // Artifacts let securityTokenRegistry; let polyToken; -let usdToken; -let securityToken; -let currentSTO; - -// App flow -let _tokenConfig; -let _mintingConfig; -let _stoConfig; - -let network; - -async function executeApp(tokenConfig, mintingConfig, stoConfig, remoteNetwork) { - _tokenConfig = tokenConfig; - _mintingConfig = mintingConfig; - _stoConfig = stoConfig; - - network = remoteNetwork; - await global.initialize(remoteNetwork); +async function executeApp(_ticker, _transferOwnership, _name, _details, _divisible) { common.logAsciiBull(); console.log("********************************************"); console.log("Welcome to the Command-Line ST-20 Generator."); @@ -65,19 +27,21 @@ async function executeApp(tokenConfig, mintingConfig, stoConfig, remoteNetwork) await setup(); try { - await step_ticker_reg(); - await step_token_deploy(); - await step_Wallet_Issuance(); - await step_STO_launch(); - await step_STO_Status(); - await step_STO_configure(); + await step_ticker_registration(_ticker); + if (!tokenLaunched) { + await step_transfer_ticker_ownership(_transferOwnership); + await step_token_deploy(_name, _details, _divisible); + } + if (typeof _divisible === 'undefined') { + await tokenManager.executeApp(tokenSymbol); + } } catch (err) { console.log(err); return; } }; -async function setup(){ +async function setup() { try { securityTokenRegistryAddress = await contracts.securityTokenRegistry(); let securityTokenRegistryABI = abis.securityTokenRegistry(); @@ -88,1049 +52,131 @@ async function setup(){ let polytokenABI = abis.polyToken(); polyToken = new web3.eth.Contract(polytokenABI, polytokenAddress); polyToken.setProvider(web3.currentProvider); - - //TODO: Use proper DAI token here - let usdTokenAddress = await contracts.usdToken(); - usdToken = new web3.eth.Contract(polytokenABI, usdTokenAddress); - usdToken.setProvider(web3.currentProvider); } catch (err) { console.log(err) - console.log('\x1b[31m%s\x1b[0m',"There was a problem getting the contracts. Make sure they are deployed to the selected network."); + console.log(chalk.red('\nThere was a problem getting the contracts. Make sure they are deployed to the selected network.')); process.exit(0); } } -async function step_ticker_reg(){ - console.log('\n\x1b[34m%s\x1b[0m',"Token Symbol Registration"); +async function step_ticker_registration(_ticker) { + console.log(chalk.blue('\nToken Symbol Registration')); - let available = false; let regFee = web3.utils.fromWei(await securityTokenRegistry.methods.getTickerRegistrationFee().call()); - let isDeployed; + let available = false; while (!available) { - console.log(chalk.green(`\nRegistering the new token symbol requires ${regFee} POLY & deducted from '${Issuer.address}', Current balance is ${(await currentBalance(Issuer.address))} POLY\n`)); + console.log(chalk.yellow(`\nRegistering the new token symbol requires ${regFee} POLY & deducted from '${Issuer.address}', Current balance is ${(web3.utils.fromWei(await polyToken.methods.balanceOf(Issuer.address).call()))} POLY\n`)); - if (typeof _tokenConfig !== 'undefined' && _tokenConfig.hasOwnProperty('symbol')) { - tokenSymbol = _tokenConfig.symbol; + if (typeof _ticker !== 'undefined') { + tokenSymbol = _ticker; + console.log(`Token Symbol: ${tokenSymbol}`); } else { - tokenSymbol = await selectTicker(true); + tokenSymbol = await selectTicker(); } let details = await securityTokenRegistry.methods.getTickerDetails(tokenSymbol).call(); - isDeployed = details[4]; if (new BigNumber(details[1]).toNumber() == 0) { + // If it has no registration date, it is available available = true; await approvePoly(securityTokenRegistryAddress, regFee); let registerTickerAction = securityTokenRegistry.methods.registerTicker(Issuer.address, tokenSymbol, ""); - await common.sendTransaction(Issuer, registerTickerAction, defaultGasPrice, 0, 1.5); + await common.sendTransaction(registerTickerAction, { factor: 1.5 }); } else if (details[0] == Issuer.address) { + // If it has registration date and its owner is Issuer available = true; + tokenLaunched = details[4]; } else { - console.log('\n\x1b[31m%s\x1b[0m',"Token Symbol has already been registered, please choose another symbol"); - } - } - - if (!isDeployed) { - if (typeof _tokenConfig === 'undefined' && readlineSync.keyInYNStrict(`Do you want to transfer the ownership of ${tokenSymbol} ticker?`)) { - let newOwner = readlineSync.question('Enter the address that will be the new owner: ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address" - }); - let transferTickerOwnershipAction = securityTokenRegistry.methods.transferTickerOwnership(newOwner, tokenSymbol); - let receipt = await common.sendTransaction(Issuer, transferTickerOwnershipAction, defaultGasPrice, 0, 1.5); - let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, receipt.logs, 'ChangeTickerOwnership'); - console.log(chalk.green(`Ownership trasferred successfully. The new owner is ${event._newOwner}`)); - process.exit(0); - } - } -} - -async function step_token_deploy(){ - // Let's check if token has already been deployed, if it has, skip to STO - let tokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (tokenAddress != "0x0000000000000000000000000000000000000000") { - console.log('\n\x1b[32m%s\x1b[0m',"Token has already been deployed at address " + tokenAddress + ". Skipping deployment."); - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, tokenAddress); - } else { - console.log('\n\x1b[34m%s\x1b[0m',"Token Creation - Token Deployment"); - - let launchFee = web3.utils.fromWei(await securityTokenRegistry.methods.getSecurityTokenLaunchFee().call()); - console.log(chalk.green(`\nToken deployment requires ${launchFee} POLY & deducted from '${Issuer.address}', Current balance is ${(await currentBalance(Issuer.address))} POLY\n`)); - - if (typeof _tokenConfig !== 'undefined' && _tokenConfig.hasOwnProperty('name')) { - tokenName = _tokenConfig.name; - } else { - tokenName = readlineSync.question('Enter the name for your new token: '); - } - if (tokenName == "") tokenName = 'default'; - - console.log("\n"); - console.log('\x1b[34m%s\x1b[0m',"Select the Token divisibility type"); - - let divisibility; - if (typeof _tokenConfig !== 'undefined' && _tokenConfig.hasOwnProperty('divisible')) { - divisibility = _tokenConfig.divisible; - } else { - let divisible = readlineSync.question('Press "N" for Non-divisible type token or hit Enter for divisible type token (Default):'); - if (divisible == 'N' || divisible == 'n') - divisibility = false; - else - divisibility = true; - } - - await approvePoly(securityTokenRegistryAddress, launchFee); - let generateSecurityTokenAction = securityTokenRegistry.methods.generateSecurityToken(tokenName, tokenSymbol, tokenDetails, divisibility); - let receipt = await common.sendTransaction(Issuer, generateSecurityTokenAction, defaultGasPrice); - let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, receipt.logs, 'NewSecurityToken'); - console.log(`Deployed Token at address: ${event._securityTokenAddress}`); - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, event._securityTokenAddress); - } -} - -async function step_Wallet_Issuance(){ - let result = await securityToken.methods.getModulesByType(MODULES_TYPES.STO).call(); - if (result.length > 0) { - console.log('\x1b[32m%s\x1b[0m',"STO has already been created at address " + result[0] + ". Skipping initial minting"); - } else { - let initialMint = await securityToken.getPastEvents('Transfer', { - filter: {from: "0x0000000000000000000000000000000000000000"}, // Using an array means OR: e.g. 20 or 23 - fromBlock: 0, - toBlock: 'latest' - }); - if (initialMint.length > 0) { - console.log('\x1b[32m%s\x1b[0m',web3.utils.fromWei(initialMint[0].returnValues.value) +" Tokens have already been minted for " + initialMint[0].returnValues.to + ". Skipping initial minting"); - } else { - console.log("\n"); - console.log('\x1b[34m%s\x1b[0m',"Token Creation - Token Minting for Issuer"); - - console.log("Before setting up the STO, you can mint any amount of tokens that will remain under your control or you can transfer to affiliates"); - - let multimint; - if (typeof _mintingConfig !== 'undefined' && _mintingConfig.hasOwnProperty('multimint')) { - multimint = _mintingConfig.multimint; - } else { - let isAffiliate = readlineSync.question('Press'+ chalk.green(` "Y" `) + 'if you have list of affiliates addresses with you otherwise hit' + chalk.green(' Enter ') + 'and get the minted tokens to a particular address: '); - multimint = (isAffiliate == "Y" || isAffiliate == "y"); - } - - if (multimint) - await multi_mint_tokens(); - else { - let mintWallet; - if (typeof _mintingConfig !== 'undefined' && _mintingConfig.hasOwnProperty('singleMint') && _mintingConfig.singleMint.hasOwnProperty('wallet')) { - mintWallet = _mintingConfig.singleMint.wallet; - } else { - mintWallet = readlineSync.question('Add the address that will hold the issued tokens to the whitelist (' + Issuer.address + '): '); - } - if (mintWallet == "") mintWallet = Issuer.address; - - let canBuyFromSTO; - if (typeof _mintingConfig !== 'undefined' && _mintingConfig.hasOwnProperty('singleMint') && _mintingConfig.singleMint.hasOwnProperty('allowedToBuy')) { - canBuyFromSTO = _mintingConfig.singleMint.allowedToBuy; - } else { - canBuyFromSTO = readlineSync.keyInYNStrict(`Is address '(${mintWallet})' allowed to buy tokens from the STO? `); - } - - // Add address to whitelist - let generalTransferManagerAddress = (await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call())[0]; - let generalTransferManagerABI = abis.generalTransferManager(); - let generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, generalTransferManagerAddress); - let modifyWhitelistAction = generalTransferManager.methods.modifyWhitelist(mintWallet,Math.floor(Date.now()/1000),Math.floor(Date.now()/1000),Math.floor(Date.now()/1000 + 31536000), canBuyFromSTO); - await common.sendTransaction(Issuer, modifyWhitelistAction, defaultGasPrice); - - // Mint tokens - if (typeof _mintingConfig !== 'undefined' && _mintingConfig.hasOwnProperty('singleMint') && _mintingConfig.singleMint.hasOwnProperty('tokenAmount')) { - issuerTokens = _mintingConfig.singleMint.tokenAmount; - } else { - issuerTokens = readlineSync.question('How many tokens do you plan to mint for the wallet you entered? (500.000): '); - } - if (issuerTokens == "") issuerTokens = '500000'; - - let mintAction = securityToken.methods.mint(mintWallet, web3.utils.toWei(issuerTokens)); - await common.sendTransaction(Issuer, mintAction, defaultGasPrice); - } + // If it has registration date and its owner is not Issuer + console.log(chalk.yellow('\nToken Symbol has already been registered, please choose another symbol')); } } } -async function multi_mint_tokens() { - //await whitelist.startWhitelisting(tokenSymbol); - shell.exec(`${__dirname}/scripts/script.sh Whitelist ${tokenSymbol} 75 ${network}`); - console.log(chalk.green(`\nCongrats! All the affiliates get succssfully whitelisted, Now its time to Mint the tokens\n`)); - console.log(chalk.red(`WARNING: `) + `Please make sure all the addresses that get whitelisted are only eligible to hold or get Security token\n`); - - shell.exec(`${__dirname}/scripts//script.sh Multimint ${tokenSymbol} 75 ${network}`); - console.log(chalk.green(`\nHurray!! Tokens get successfully Minted and transferred to token holders`)); -} - -async function step_STO_launch() { - console.log("\n"); - console.log('\x1b[34m%s\x1b[0m',"Token Creation - STO Configuration"); - - let result = await securityToken.methods.getModulesByType(MODULES_TYPES.STO).call(); - if (result.length > 0) { - STO_Address = result[0]; - let stoModuleData = await securityToken.methods.getModule(STO_Address).call(); - selectedSTO = web3.utils.toAscii(stoModuleData[0]).replace(/\u0000/g, ''); - console.log('\x1b[32m%s\x1b[0m',selectedSTO + " has already been created at address " + STO_Address + ". Skipping STO creation"); - switch (selectedSTO) { - case 'CappedSTO': - let cappedSTOABI = abis.cappedSTO(); - currentSTO = new web3.eth.Contract(cappedSTOABI,STO_Address); - break; - case 'USDTieredSTO': - let usdTieredSTOABI = abis.usdTieredSTO(); - currentSTO = new web3.eth.Contract(usdTieredSTOABI,STO_Address); - break; - } - } else { - let index; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('type')) { - index = _stoConfig.type; - } else { - let options = ['Capped STO', 'USD Tiered STO', 'Select STO later']; - index = readlineSync.keyInSelect(options, 'What type of STO do you want?', { cancel: false }); - } - switch (index) { - case 0: - selectedSTO = 'CappedSTO'; - await cappedSTO_launch(); - break; - case 1: - selectedSTO = 'USDTieredSTO'; - await usdTieredSTO_launch(); - break; - case 2: - process.exit(0); - break; - } - } -} - -async function step_STO_Status() { - switch (selectedSTO) { - case 'CappedSTO': - await cappedSTO_status(); - break; - case 'USDTieredSTO': - await usdTieredSTO_status(); - break; - } -} - -async function step_STO_configure() { - switch (selectedSTO) { - case 'CappedSTO': - break; - case 'USDTieredSTO': - await usdTieredSTO_configure(); - break; - } -} - -//////////////// -// Capped STO // -//////////////// -async function cappedSTO_launch() { - console.log("\n"); - console.log('\x1b[34m%s\x1b[0m',"Token Creation - Capped STO in No. of Tokens"); - - let stoFee = cappedSTOFee; - let contractBalance = await polyToken.methods.balanceOf(securityToken._address).call(); - let requiredAmount = web3.utils.toWei(stoFee.toString()); - if (parseInt(contractBalance) < parseInt(requiredAmount)) { - let transferAmount = parseInt(requiredAmount) - parseInt(contractBalance); - let ownerBalance = await polyToken.methods.balanceOf(Issuer.address).call(); - if(parseInt(ownerBalance) < transferAmount) { - console.log(chalk.red(`\n**************************************************************************************************************************************************`)); - console.log(chalk.red(`Not enough balance to pay the CappedSTO fee, Requires ${(new BigNumber(transferAmount).dividedBy(new BigNumber(10).pow(18))).toNumber()} POLY but have ${(new BigNumber(ownerBalance).dividedBy(new BigNumber(10).pow(18))).toNumber()} POLY. Access POLY faucet to get the POLY to complete this txn`)); - console.log(chalk.red(`**************************************************************************************************************************************************\n`)); - process.exit(0); - } else { - let transferAction = polyToken.methods.transfer(securityToken._address, new BigNumber(transferAmount)); - let receipt = await common.sendTransaction(Issuer, transferAction, defaultGasPrice, 0, 2); - let event = common.getEventFromLogs(polyToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(event._value))}`) - } - } - - let cap; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('cap')) { - cap = _stoConfig.cap.toString(); - } else { - cap = readlineSync.question('How many tokens do you plan to sell on the STO? (500.000): '); - } - if (cap == "") cap = '500000'; - - let oneMinuteFromNow = BigNumber((Math.floor(Date.now() / 1000) + 60)); - let startTime; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('startTime')) { - startTime = _stoConfig.startTime; - } else { - startTime = readlineSync.question('Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): '); - } - if (startTime == "") startTime = oneMinuteFromNow; - - let oneMonthFromNow = BigNumber((Math.floor(Date.now()/1000)+ (30 * 24 * 60 * 60))); - let endTime; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('endTime')) { - endTime = _stoConfig.endTime; - } else { - endTime = readlineSync.question('Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): '); - } - if (endTime == "") endTime = oneMonthFromNow; - - let wallet; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('wallet')) { - wallet = _stoConfig.wallet; - } else { - wallet = readlineSync.question('Enter the address that will receive the funds from the STO (' + Issuer.address + '): '); - } - if (wallet == "") wallet = Issuer.address; - - let raiseType; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('raiseType')) { - raiseType = [_stoConfig.raiseType]; - } else { - raiseType = readlineSync.question('Enter' + chalk.green(` P `) + 'for POLY raise or leave empty for Ether raise (E):'); - if (raiseType.toUpperCase() == 'P' ) { - raiseType = [1]; - } else { - raiseType = [0]; - } - } - - let rate; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('rate')) { - rate = _stoConfig.rate; - } else { - rate = readlineSync.question(`Enter the rate (1 ${(raiseType == 1 ? 'POLY' : 'ETH')} = X ST) for the STO (1000): `); - } - if (rate == "") rate = 1000; - - let bytesSTO = web3.eth.abi.encodeFunctionCall( { - name: 'configure', - type: 'function', - inputs: [ - { - type: 'uint256', - name: '_startTime' - },{ - type: 'uint256', - name: '_endTime' - },{ - type: 'uint256', - name: '_cap' - },{ - type: 'uint256', - name: '_rate' - },{ - type: 'uint8[]', - name: '_fundRaiseTypes' - },{ - type: 'address', - name: '_fundsReceiver' - } - ] - }, [startTime, endTime, web3.utils.toWei(cap), rate, raiseType, wallet]); - - let cappedSTOFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, MODULES_TYPES.STO, "CappedSTO"); - let addModuleAction = securityToken.methods.addModule(cappedSTOFactoryAddress, bytesSTO, new BigNumber(stoFee).times(new BigNumber(10).pow(18)), 0); - let receipt = await common.sendTransaction(Issuer, addModuleAction, defaultGasPrice); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(`STO deployed at address: ${event._module}`); - - STO_Address = event._module; - let cappedSTOABI = abis.cappedSTO(); - currentSTO = new web3.eth.Contract(cappedSTOABI, STO_Address); -} - -async function cappedSTO_status() { - let displayStartTime = await currentSTO.methods.startTime().call(); - let displayEndTime = await currentSTO.methods.endTime().call(); - let displayRate = await currentSTO.methods.rate().call(); - let displayCap = await currentSTO.methods.cap().call(); - let displayWallet = await currentSTO.methods.wallet().call(); - let displayRaiseType; - let displayFundsRaised; - let displayWalletBalance; - let raiseType = await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES.ETH).call(); - if (raiseType) { - displayRaiseType = 'ETH'; - displayFundsRaised = await currentSTO.methods.fundsRaised(FUND_RAISE_TYPES.ETH).call(); - displayWalletBalance = web3.utils.fromWei(await web3.eth.getBalance(displayWallet)); - } else { - displayRaiseType = 'POLY'; - displayFundsRaised = await currentSTO.methods.fundsRaised(FUND_RAISE_TYPES.POLY).call(); - displayWalletBalance = await currentBalance(displayWallet); - } - let displayTokensSold = await currentSTO.methods.totalTokensSold().call(); - let displayInvestorCount = await currentSTO.methods.investorCount().call(); - let displayTokenSymbol = await securityToken.methods.symbol().call(); - - let formattedCap = BigNumber(web3.utils.fromWei(displayCap)); - let formattedSold = BigNumber(web3.utils.fromWei(displayTokensSold)); - - let now = Math.floor(Date.now()/1000); - let timeTitle; - let timeRemaining; - - if (now < displayStartTime) { - timeTitle = "STO starts in: "; - timeRemaining = displayStartTime - now; - } else { - timeTitle = "Time remaining:"; - timeRemaining = displayEndTime - now; - } - - timeRemaining = common.convertToDaysRemaining(timeRemaining); - - console.log(` - ***** STO Information ***** - - Address: ${STO_Address} - - Raise Cap: ${web3.utils.fromWei(displayCap)} ${displayTokenSymbol.toUpperCase()} - - Start Time: ${new Date(displayStartTime * 1000)} - - End Time: ${new Date(displayEndTime * 1000)} - - Raise Type: ${displayRaiseType} - - Rate: 1 ${displayRaiseType} = ${displayRate} ${displayTokenSymbol.toUpperCase()} - - Wallet: ${displayWallet} - - Wallet Balance: ${displayWalletBalance} ${displayRaiseType} - -------------------------------------- - - ${timeTitle} ${timeRemaining} - - Funds raised: ${web3.utils.fromWei(displayFundsRaised)} ${displayRaiseType} - - Tokens sold: ${web3.utils.fromWei(displayTokensSold)} ${displayTokenSymbol.toUpperCase()} - - Tokens remaining: ${formattedCap.minus(formattedSold).toNumber()} ${displayTokenSymbol.toUpperCase()} - - Investor count: ${displayInvestorCount} - `); - - console.log(chalk.green(`\n${(await currentBalance(Issuer.address))} POLY balance remaining at issuer address ${Issuer.address}`)); -} - -//////////////////// -// USD Tiered STO // -//////////////////// -function fundingConfigUSDTieredSTO() { - let funding = {}; - - let selectedFunding; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('fundingType')) { - selectedFunding = _stoConfig.fundingType; - } else { - selectedFunding = readlineSync.question('Enter' + chalk.green(` P `) + 'for POLY raise,' + chalk.green(` D `) + 'for DAI raise,' + chalk.green(` E `) + 'for Ether raise or any combination of them (i.e.'+ chalk.green(` PED `) + 'for all): ').toUpperCase(); - } - - funding.raiseType = []; - if (selectedFunding.includes('E')) { - funding.raiseType.push(FUND_RAISE_TYPES.ETH); - } - if (selectedFunding.includes('P')) { - funding.raiseType.push(FUND_RAISE_TYPES.POLY); - } - if (selectedFunding.includes('D')) { - funding.raiseType.push(FUND_RAISE_TYPES.DAI); - } - if (funding.raiseType.length == 0) { - funding.raiseType = [FUND_RAISE_TYPES.ETH, FUND_RAISE_TYPES.POLY, FUND_RAISE_TYPES.DAI]; - } - - return funding; -} - -function addressesConfigUSDTieredSTO(usdTokenRaise) { - let addresses = {}; - - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('wallet')) { - addresses.wallet = _stoConfig.wallet; - } else { - addresses.wallet = readlineSync.question('Enter the address that will receive the funds from the STO (' + Issuer.address + '): ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - } - if (addresses.wallet == "") addresses.wallet = Issuer.address; - - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('reserveWallet')) { - addresses.reserveWallet = _stoConfig.reserveWallet; - } else { - addresses.reserveWallet = readlineSync.question('Enter the address that will receive remaining tokens in the case the cap is not met (' + Issuer.address + '): ', { - limit: function(input) { +async function step_transfer_ticker_ownership(_transferOwnership) { + let newOwner = null; + if (typeof _transferOwnership !== 'undefined' && _transferOwnership != 'false') { + newOwner = _transferOwnership; + console.log(`Transfer ownership to: ${newOwner}`); + } else if (_transferOwnership != 'false' && readlineSync.keyInYNStrict(`Do you want to transfer the ownership of ${tokenSymbol} ticker?`)) { + newOwner = readlineSync.question('Enter the address that will be the new owner: ', { + limit: function (input) { return web3.utils.isAddress(input); }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - } - if (addresses.reserveWallet == "") addresses.reserveWallet = Issuer.address; - - if (usdTokenRaise) { - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('usdToken')) { - addresses.usdToken = _stoConfig.usdToken; - } else { - addresses.usdToken = readlineSync.question('Enter the address of the USD Token or stable coin (' + usdToken.options.address + '): ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: usdToken.options.address - }); - } - if (addresses.usdToken == "") addresses.usdToken = usdToken.options.address; - } else { - addresses.usdToken = '0x0000000000000000000000000000000000000000'; - } - - return addresses; -} - -function tiersConfigUSDTieredSTO(polyRaise) { - let tiers = {}; - - let defaultTiers = 3; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('numberOfTiers')) { - tiers.tiers = _stoConfig.numberOfTiers; - } else { - tiers.tiers = readlineSync.questionInt(`Enter the number of tiers for the STO? (${defaultTiers}): `, { - limit: function(input) { - return input > 0; - }, - limitMessage: 'Must be greater than zero', - defaultInput: defaultTiers + limitMessage: "Must be a valid address" }); } - let defaultTokensPerTier = [190000000, 100000000, 200000000]; - let defaultRatePerTier = ['0.05', '0.10', '0.15']; - let defaultTokensPerTierDiscountPoly = [90000000, 50000000, 100000000]; - let defaultRatePerTierDiscountPoly = ['0.025', '0.05', '0.075']; - tiers.tokensPerTier = []; - tiers.ratePerTier = []; - tiers.tokensPerTierDiscountPoly = []; - tiers.ratePerTierDiscountPoly = []; - for (let i = 0; i < tiers.tiers; i++) { - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('tokensPerTiers') && i < _stoConfig.tokensPerTiers.length) { - tiers.tokensPerTier[i] = web3.utils.toWei(_stoConfig.tokensPerTiers[i].toString()); - } else { - tiers.tokensPerTier[i] = web3.utils.toWei(readlineSync.question(`How many tokens do you plan to sell on tier No. ${i+1}? (${defaultTokensPerTier[i]}): `, { - limit: function(input) { - return parseFloat(input) > 0; - }, - limitMessage: 'Must be greater than zero', - defaultInput: defaultTokensPerTier[i] - })); - } - - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('ratePerTiers') && i < _stoConfig.ratePerTiers.length) { - tiers.ratePerTier[i] = web3.utils.toWei(_stoConfig.ratePerTiers[i].toString()); - } else { - tiers.ratePerTier[i] = web3.utils.toWei(readlineSync.question(`What is the USD per token rate for tier No. ${i+1}? (${defaultRatePerTier[i]}): `, { - limit: function(input) { - return parseFloat(input) > 0; - }, - limitMessage: 'Must be greater than zero', - defaultInput: defaultRatePerTier[i] - })); - } - - let isTPTDPDefined = (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('discountedTokensPerTiers') && i < _stoConfig.discountedTokensPerTiers.length); //If it's defined by config file - let isRPTDPDefined = (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('discountedRatePerTiers') && i < _stoConfig.discountedRatePerTiers.length); //If it's defined by config file - //If funds can be raised in POLY and discounts are defined in config file or are choosen by user - if (polyRaise && ((isTPTDPDefined && isRPTDPDefined) || readlineSync.keyInYNStrict(`Do you plan to have a discounted rate for POLY investments for tier No. ${i+1}? `))) { - if (isTPTDPDefined) { - tiers.tokensPerTierDiscountPoly[i] = web3.utils.toWei(_stoConfig.discountedTokensPerTiers[i].toString()); - } else { - tiers.tokensPerTierDiscountPoly[i] = web3.utils.toWei(readlineSync.question(`How many of those tokens do you plan to sell at discounted rate on tier No. ${i+1}? (${defaultTokensPerTierDiscountPoly[i]}): `, { - limit: function(input) { - return new BigNumber(web3.utils.toWei(input)).lte(tiers.tokensPerTier[i]) - }, - limitMessage: 'Must be less than the No. of tokens of the tier', - defaultInput: defaultTokensPerTierDiscountPoly[i] - })); - } - - if (isRPTDPDefined) { - tiers.ratePerTierDiscountPoly[i] = web3.utils.toWei(_stoConfig.discountedRatePerTiers[i].toString()); - } else { - tiers.ratePerTierDiscountPoly[i] = web3.utils.toWei(readlineSync.question(`What is the discounted rate for tier No. ${i+1}? (${defaultRatePerTierDiscountPoly[i]}): `, { - limit: function(input) { - return new BigNumber(web3.utils.toWei(input)).lte(tiers.ratePerTier[i]) - }, - limitMessage: 'Must be less than the rate of the tier', - defaultInput: defaultRatePerTierDiscountPoly[i] - })); - } - } else { - tiers.tokensPerTierDiscountPoly[i] = 0; - tiers.ratePerTierDiscountPoly[i] = 0; - } - } - - return tiers; -} - -function timesConfigUSDTieredSTO() { - let times = {}; - - let oneMinuteFromNow = Math.floor(Date.now() / 1000) + 60; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('startTime')) { - times.startTime = _stoConfig.startTime; - } else { - times.startTime = readlineSync.questionInt('Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): ', { - limit: function(input) { - return input > Math.floor(Date.now() / 1000); - }, - limitMessage: "Must be a future time", - defaultInput: oneMinuteFromNow - }); - } - if (times.startTime == "") times.startTime = oneMinuteFromNow; - - let oneMonthFromNow = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60); - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('endTime')) { - times.endTime = _stoConfig.endTime; - } else { - times.endTime = readlineSync.questionInt('Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): ', { - limit: function(input) { - return input > times.startTime; - }, - limitMessage: "Must be greater than Start Time", - defaultInput: oneMonthFromNow - }); + if (newOwner) { + let transferTickerOwnershipAction = securityTokenRegistry.methods.transferTickerOwnership(newOwner, tokenSymbol); + let receipt = await common.sendTransaction(transferTickerOwnershipAction, { factor: 1.5 }); + let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, receipt.logs, 'ChangeTickerOwnership'); + console.log(chalk.green(`Ownership trasferred successfully. The new owner is ${event._newOwner}`)); + process.exit(0); } - if (times.endTime == "") times.endTime = oneMonthFromNow; - - return times; } -function limitsConfigUSDTieredSTO() { - let limits = {}; +async function step_token_deploy(_name, _details, _divisible) { + console.log(chalk.blue('\nToken Creation - Token Deployment')); - let defaultMinimumInvestment = 5; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('minimumInvestmentUSD')) { - limits.minimumInvestmentUSD = web3.utils.toWei(_stoConfig.minimumInvestmentUSD.toString()); - } else { - limits.minimumInvestmentUSD = web3.utils.toWei(readlineSync.question(`What is the minimum investment in USD? (${defaultMinimumInvestment}): `, { - limit: function(input) { - return parseInt(input) > 0; - }, - limitMessage: "Must be greater than zero", - defaultInput: defaultMinimumInvestment - })); - } + let launchFee = web3.utils.fromWei(await securityTokenRegistry.methods.getSecurityTokenLaunchFee().call()); + console.log(chalk.green(`\nToken deployment requires ${launchFee} POLY & deducted from '${Issuer.address}', Current balance is ${(web3.utils.fromWei(await polyToken.methods.balanceOf(Issuer.address).call()))} POLY\n`)); - let nonAccreditedLimit = 2500; - if (typeof _stoConfig !== 'undefined' && _stoConfig.hasOwnProperty('nonAccreditedLimitUSD')) { - limits.nonAccreditedLimitUSD = web3.utils.toWei(_stoConfig.nonAccreditedLimitUSD.toString()); + let tokenName; + if (typeof _name !== 'undefined') { + tokenName = _name; + console.log(`Token Name: ${tokenName}`); } else { - limits.nonAccreditedLimitUSD = web3.utils.toWei(readlineSync.question(`What is the default limit for non accredited investors in USD? (${nonAccreditedLimit}): `, { - limit: function(input) { - return new BigNumber(web3.utils.toWei(input)).gte(limits.minimumInvestmentUSD); - }, - limitMessage: "Must be greater than minimum investment", - defaultInput: nonAccreditedLimit - })); - } - - return limits; -} - -async function usdTieredSTO_launch() { - console.log("\n"); - console.log('\x1b[34m%s\x1b[0m',"Token Creation - USD Tiered STO"); - - let stoFee = usdTieredSTOFee; - let contractBalance = await polyToken.methods.balanceOf(securityToken._address).call(); - let requiredAmount = web3.utils.toWei(stoFee.toString(), "ether"); - if (new web3.utils.BN(contractBalance).lt(new web3.utils.BN(requiredAmount))) { - let transferAmount = (new web3.utils.BN(requiredAmount)).sub(new web3.utils.BN(contractBalance)); - let ownerBalance = new web3.utils.BN(await polyToken.methods.balanceOf(Issuer.address).call()); - if (ownerBalance.lt(transferAmount)) { - console.log(chalk.red(`\n**************************************************************************************************************************************************`)); - console.log(chalk.red(`Not enough balance to pay the ${selectedSTO} fee, Requires ${web3.utils.fromWei(transferAmount)} POLY but have ${web3.utils.fromWei(ownerBalance)} POLY. Access POLY faucet to get the POLY to complete this txn`)); - console.log(chalk.red(`**************************************************************************************************************************************************\n`)); - process.exit(0); - } else { - let transferAction = polyToken.methods.transfer(securityToken._address, transferAmount); - let receipt = await common.sendTransaction(Issuer, transferAction, defaultGasPrice, 0, 2); - let event = common.getEventFromLogs(polyToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(event._value))}`) - } - } - - let funding = fundingConfigUSDTieredSTO(); - let addresses = addressesConfigUSDTieredSTO(funding.raiseType.includes(FUND_RAISE_TYPES.DAI)); - let tiers = tiersConfigUSDTieredSTO(funding.raiseType.includes(FUND_RAISE_TYPES.POLY)); - let limits = limitsConfigUSDTieredSTO(); - let times = timesConfigUSDTieredSTO(); - let bytesSTO = web3.eth.abi.encodeFunctionCall( { - name: 'configure', - type: 'function', - inputs: [ - { - type: 'uint256', - name: '_startTime' - },{ - type: 'uint256', - name: '_endTime' - },{ - type: 'uint256[]', - name: '_ratePerTier' - },{ - type: 'uint256[]', - name: '_ratePerTierDiscountPoly' - },{ - type: 'uint256[]', - name: '_tokensPerTier' - },{ - type: 'uint256[]', - name: '_tokensPerTierDiscountPoly' - },{ - type: 'uint256', - name: '_nonAccreditedLimitUSD' - },{ - type: 'uint256', - name: '_minimumInvestmentUSD' - },{ - type: 'uint8[]', - name: '_fundRaiseTypes' - },{ - type: 'address', - name: '_wallet' - },{ - type: 'address', - name: '_reserveWallet' - },{ - type: 'address', - name: '_usdToken' - } - ] - }, [times.startTime, - times.endTime, - tiers.ratePerTier, - tiers.ratePerTierDiscountPoly, - tiers.tokensPerTier, - tiers.tokensPerTierDiscountPoly, - limits.nonAccreditedLimitUSD, - limits.minimumInvestmentUSD, - funding.raiseType, - addresses.wallet, - addresses.reserveWallet, - addresses.usdToken - ]); - - let usdTieredSTOFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, MODULES_TYPES.STO, 'USDTieredSTO'); - let addModuleAction = securityToken.methods.addModule(usdTieredSTOFactoryAddress, bytesSTO, new BigNumber(stoFee).times(new BigNumber(10).pow(18)), 0); - let receipt = await common.sendTransaction(Issuer, addModuleAction, defaultGasPrice); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(`STO deployed at address: ${event._module}`); - - STO_Address = event._module; - let usdTieredSTOABI = abis.usdTieredSTO(); - currentSTO = new web3.eth.Contract(usdTieredSTOABI,STO_Address); -} - -async function usdTieredSTO_status() { - let displayStartTime = await currentSTO.methods.startTime().call(); - let displayEndTime = await currentSTO.methods.endTime().call(); - let displayCurrentTier = parseInt(await currentSTO.methods.currentTier().call()) + 1; - let displayNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call()); - let displayMinimumInvestmentUSD = web3.utils.fromWei(await currentSTO.methods.minimumInvestmentUSD().call()); - let displayWallet = await currentSTO.methods.wallet().call(); - let displayReserveWallet = await currentSTO.methods.reserveWallet().call(); - let displayTokensSold = web3.utils.fromWei(await currentSTO.methods.getTokensSold().call()); - let displayInvestorCount = await currentSTO.methods.investorCount().call(); - let displayIsFinalized = await currentSTO.methods.isFinalized().call() ? "YES" : "NO"; - let displayTokenSymbol = await securityToken.methods.symbol().call(); - - let tiersLength = await currentSTO.methods.getNumberOfTiers().call();; - - let raiseTypes = []; - for (const fundType in FUND_RAISE_TYPES) { - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES[fundType]).call()) { - raiseTypes.push(fundType); - } - } - - let displayTiers = ""; - let displayMintedPerTier = ""; - for (let t = 0; t < tiersLength; t++) { - let ratePerTier = await currentSTO.methods.ratePerTier(t).call(); - let tokensPerTierTotal = await currentSTO.methods.tokensPerTierTotal(t).call(); - let mintedPerTierTotal = await currentSTO.methods.mintedPerTierTotal(t).call(); - - let displayMintedPerTierPerType = ""; - let displayDiscountTokens = ""; - for (const type of raiseTypes) { - let displayDiscountMinted = ""; - if (type == 'POLY') { - let tokensPerTierDiscountPoly = await currentSTO.methods.tokensPerTierDiscountPoly(t).call(); - if (tokensPerTierDiscountPoly > 0) { - let ratePerTierDiscountPoly = await currentSTO.methods.ratePerTierDiscountPoly(t).call(); - let mintedPerTierDiscountPoly = await currentSTO.methods.mintedPerTierDiscountPoly(t).call(); - displayDiscountTokens = ` - Tokens at discounted rate: ${web3.utils.fromWei(tokensPerTierDiscountPoly)} ${displayTokenSymbol} - Discounted rate: ${web3.utils.fromWei(ratePerTierDiscountPoly, 'ether')} USD per Token`; - - displayDiscountMinted = `(${web3.utils.fromWei(mintedPerTierDiscountPoly)} ${displayTokenSymbol} at discounted rate)`; - } - } - - let mintedPerTier = await currentSTO.methods.mintedPerTier(FUND_RAISE_TYPES[type], t).call(); - displayMintedPerTierPerType += ` - Sold for ${type}:\t\t ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; - } - - displayTiers += ` - - Tier ${t+1}: - Tokens: ${web3.utils.fromWei(tokensPerTierTotal)} ${displayTokenSymbol} - Rate: ${web3.utils.fromWei(ratePerTier)} USD per Token` - + displayDiscountTokens; - - displayMintedPerTier += ` - - Tokens minted in Tier ${t+1}: ${web3.utils.fromWei(mintedPerTierTotal)} ${displayTokenSymbol}` - + displayMintedPerTierPerType; + tokenName = readlineSync.question('Enter the name for your new token: ', { defaultInput: 'default' }); } - let displayFundsRaisedUSD = web3.utils.fromWei(await currentSTO.methods.fundsRaisedUSD().call()); - - let displayWalletBalancePerType = ''; - let displayReserveWalletBalancePerType = ''; - let displayFundsRaisedPerType = ''; - let displayTokensSoldPerType = ''; - for (const type of raiseTypes) { - let balance = await getBalance(displayWallet, type); - let walletBalance = web3.utils.fromWei(balance); - let walletBalanceUSD = web3.utils.fromWei(await currentSTO.methods.convertToUSD(FUND_RAISE_TYPES[type], balance).call()); - displayWalletBalancePerType += ` - Balance ${type}:\t\t ${walletBalance} ${type} (${walletBalanceUSD} USD)`; - - balance = await getBalance(displayReserveWallet, type); - let reserveWalletBalance = web3.utils.fromWei(balance); - let reserveWalletBalanceUSD = web3.utils.fromWei(await currentSTO.methods.convertToUSD(FUND_RAISE_TYPES[type], balance).call()); - displayReserveWalletBalancePerType += ` - Balance ${type}:\t\t ${reserveWalletBalance} ${type} (${reserveWalletBalanceUSD} USD)`; - - let fundsRaised = web3.utils.fromWei(await currentSTO.methods.fundsRaised(FUND_RAISE_TYPES[type]).call()); - displayFundsRaisedPerType += ` - ${type}:\t\t\t ${fundsRaised} ${type}`; - - //Only show sold for if more than one raise type are allowed - if (raiseTypes.length > 1) { - let tokensSoldPerType = web3.utils.fromWei(await currentSTO.methods.getTokensSoldFor(FUND_RAISE_TYPES[type]).call()); - displayTokensSoldPerType += ` - Sold for ${type}:\t\t ${tokensSoldPerType} ${displayTokenSymbol}`; - } - } - - let displayRaiseType = raiseTypes.join(' - '); - - let now = Math.floor(Date.now()/1000); - let timeTitle; - let timeRemaining; - if (now < displayStartTime) { - timeTitle = "STO starts in: "; - timeRemaining = displayStartTime - now; + let tokenDetails; + if (typeof _details !== 'undefined') { + tokenDetails = _details; + console.log(`Token details: ${tokenDetails.toString()}`) } else { - timeTitle = "Time remaining:"; - timeRemaining = displayEndTime - now; + tokenDetails = readlineSync.question('Enter off-chain details of the token (i.e. Dropbox folder url): '); } - timeRemaining = common.convertToDaysRemaining(timeRemaining); - - console.log(` - ****************** STO Information ****************** - - Address: ${STO_Address} - - Start Time: ${new Date(displayStartTime * 1000)} - - End Time: ${new Date(displayEndTime * 1000)} - - Raise Type: ${displayRaiseType} - - Tiers: ${tiersLength}` - + displayTiers + ` - - Minimum Investment: ${displayMinimumInvestmentUSD} USD - - Non Accredited Limit: ${displayNonAccreditedLimitUSD} USD - - Wallet: ${displayWallet}` - + displayWalletBalancePerType + ` - - Reserve Wallet: ${displayReserveWallet}` - + displayReserveWalletBalancePerType + ` - - -------------------------------------- - - ${timeTitle} ${timeRemaining} - - Is Finalized: ${displayIsFinalized} - - Tokens Sold: ${displayTokensSold} ${displayTokenSymbol}` - + displayTokensSoldPerType + ` - - Current Tier: ${displayCurrentTier}` - + displayMintedPerTier + ` - - Investor count: ${displayInvestorCount} - - Funds Raised` - + displayFundsRaisedPerType + ` - USD: ${displayFundsRaisedUSD} USD - `); - - console.log(chalk.green(`\n${(await currentBalance(Issuer.address))} POLY balance remaining at issuer address ${Issuer.address}`)); -} - -async function usdTieredSTO_configure() { - console.log("\n"); - console.log('\x1b[34m%s\x1b[0m',"STO Configuration - USD Tiered STO"); - - let isFinalized = await currentSTO.methods.isFinalized().call(); - if (isFinalized) { - console.log(chalk.red(`STO is finalized`)); + let divisibility; + if (typeof _divisible !== 'undefined') { + divisibility = _divisible.toString() == 'true'; + console.log(`Divisible: ${divisibility.toString()}`) } else { - let options = []; - options.push('Finalize STO', - 'Change accredited account', 'Change accredited in batch', - 'Change non accredited limit for an account', 'Change non accredited limits in batch'); - - // If STO is not started, you can modify configuration - let now = Math.floor(Date.now() / 1000); - let startTime = await currentSTO.methods.startTime().call.call(); - if (now < startTime) { - options.push('Modify times configuration', 'Modify tiers configuration', 'Modify addresses configuration', - 'Modify limits configuration', 'Modify funding configuration'); - } - - if (typeof _stoConfig === 'undefined') { - let index = readlineSync.keyInSelect(options, 'What do you want to do?'); - switch (index) { - case 0: - let reserveWallet = await currentSTO.methods.reserveWallet().call(); - let isVerified = await securityToken.methods.verifyTransfer("0x0000000000000000000000000000000000000000", reserveWallet, 0, web3.utils.fromAscii("")).call(); - if (isVerified) { - if (readlineSync.keyInYNStrict()) { - let finalizeAction = currentSTO.methods.finalize(); - await common.sendTransaction(Issuer, finalizeAction, defaultGasPrice); - } - } else { - console.log(chalk.red(`Reserve wallet (${reserveWallet}) is not able to receive remaining tokens. Check if this address is whitelisted.`)); - } - break; - case 1: - let investor = readlineSync.question('Enter the address to change accreditation: '); - let isAccredited = readlineSync.keyInYNStrict(`Is ${investor} accredited?`); - let investors = [investor]; - let accredited = [isAccredited]; - let changeAccreditedAction = currentSTO.methods.changeAccredited(investors, accredited); - // 2 GAS? - await common.sendTransaction(Issuer, changeAccreditedAction, defaultGasPrice); - break; - case 2: - shell.exec(`${__dirname}/scripts/script.sh Accredit ${tokenSymbol} 75 ${network}`); - break; - case 3: - let account = readlineSync.question('Enter the address to change non accredited limit: '); - let limit = readlineSync.question(`Enter the limit in USD: `); - let accounts = [account]; - let limits = [web3.utils.toWei(limit)]; - let changeNonAccreditedLimitAction = currentSTO.methods.changeNonAccreditedLimit(accounts, limits); - // 2 GAS? - await common.sendTransaction(Issuer, changeNonAccreditedLimitAction, defaultGasPrice); - break; - case 4: - shell.exec(`${__dirname}/scripts/script.sh NonAccreditedLimit ${tokenSymbol} 75 ${network}`); - break; - case 5: - await modfifyTimes(); - await usdTieredSTO_status(); - break; - case 6: - await modfifyTiers(); - await usdTieredSTO_status(); - break; - case 7: - await modfifyAddresses(); - await usdTieredSTO_status(); - break; - case 8: - await modfifyLimits(); - await usdTieredSTO_status(); - break; - case 9: - await modfifyFunding(); - await usdTieredSTO_status(); - break; - } - } + let divisible = readlineSync.question('Press "N" for Non-divisible type token or hit Enter for divisible type token (Default): '); + divisibility = (divisible != 'N' && divisible != 'n'); } -} - -async function modfifyTimes() { - let times = timesConfigUSDTieredSTO(); - let modifyTimesAction = currentSTO.methods.modifyTimes(times.startTime, times.endTime); - await common.sendTransaction(Issuer, modifyTimesAction, defaultGasPrice); -} - -async function modfifyLimits() { - let limits = limitsConfigUSDTieredSTO(); - let modifyLimitsAction = currentSTO.methods.modifyLimits(limits.nonAccreditedLimitUSD, limits.minimumInvestmentUSD); - await common.sendTransaction(Issuer, modifyLimitsAction, defaultGasPrice); -} -async function modfifyFunding() { - let funding = fundingConfigUSDTieredSTO(); - let modifyFundingAction = currentSTO.methods.modifyFunding(funding.raiseType); - await common.sendTransaction(Issuer, modifyFundingAction, defaultGasPrice); -} - -async function modfifyAddresses() { - let addresses = addressesConfigUSDTieredSTO(await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES.DAI).call()); - let modifyAddressesAction = currentSTO.methods.modifyAddresses(addresses.wallet, addresses.reserveWallet, addresses.usdToken); - await common.sendTransaction(Issuer, modifyAddressesAction, defaultGasPrice); -} - -async function modfifyTiers() { - let tiers = tiersConfigUSDTieredSTO(await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES.POLY).call()); - let modifyTiersAction = currentSTO.methods.modifyTiers( - tiers.ratePerTier, - tiers.ratePerTierDiscountPoly, - tiers.tokensPerTier, - tiers.tokensPerTierDiscountPoly, - ); - await common.sendTransaction(Issuer, modifyTiersAction, defaultGasPrice); + await approvePoly(securityTokenRegistryAddress, launchFee); + let generateSecurityTokenAction = securityTokenRegistry.methods.generateSecurityToken(tokenName, tokenSymbol, tokenDetails, divisibility); + let receipt = await common.sendTransaction(generateSecurityTokenAction); + let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, receipt.logs, 'NewSecurityToken'); + console.log(chalk.green(`Security Token has been successfully deployed at address ${event._securityTokenAddress}`)); } ////////////////////// // HELPER FUNCTIONS // ////////////////////// -async function getBalance(from, type) { - switch (type) { - case 'ETH': - return await web3.eth.getBalance(from); - case 'POLY': - return await polyToken.methods.balanceOf(from).call(); - case 'DAI': - return await usdToken.methods.balanceOf(from).call(); - } -} - -async function currentBalance(from) { - let balance = await polyToken.methods.balanceOf(from).call(); - let balanceInPoly = new BigNumber(balance).dividedBy(new BigNumber(10).pow(18)); - return balanceInPoly; -} - -async function selectTicker(includeCreate) { +async function selectTicker() { let result; - let userTickers = (await securityTokenRegistry.methods.getTickersByOwner(Issuer.address).call()).map(function (t) {return web3.utils.hexToAscii(t)}); + let userTickers = (await securityTokenRegistry.methods.getTickersByOwner(Issuer.address).call()).map(t => web3.utils.hexToAscii(t)); let options = await Promise.all(userTickers.map(async function (t) { let tickerDetails = await securityTokenRegistry.methods.getTickerDetails(t).call(); - let tickerInfo = tickerDetails[4] ? 'Token launched' : `Expires at: ${moment.unix(tickerDetails[2]).format('MMMM Do YYYY, HH:mm:ss')}`; + let tickerInfo; + if (tickerDetails[4]) { + tickerInfo = `Token launched at ${(await securityTokenRegistry.methods.getSecurityTokenAddress(t).call())}`; + } else { + tickerInfo = `Expires at ${moment.unix(tickerDetails[2]).format('MMMM Do YYYY, HH:mm:ss')}`; + } return `${t} ${tickerInfo}`; })); - if (includeCreate) { - options.push('Register a new ticker'); - } + options.push('Register a new ticker'); let index = readlineSync.keyInSelect(options, 'Select a ticker:'); if (index == -1) { process.exit(0); - } else if (includeCreate && index == options.length - 1) { + } else if (index == options.length - 1) { result = readlineSync.question('Enter a symbol for your new ticker: '); } else { result = userTickers[index]; @@ -1141,26 +187,26 @@ async function selectTicker(includeCreate) { async function approvePoly(spender, fee) { polyBalance = await polyToken.methods.balanceOf(Issuer.address).call(); - let requiredAmount = web3.utils.toWei(fee.toString(), "ether"); + let requiredAmount = web3.utils.toWei(fee.toString()); if (parseInt(polyBalance) >= parseInt(requiredAmount)) { - let allowance = await polyToken.methods.allowance(spender, Issuer.address).call(); - if (allowance == web3.utils.toWei(fee.toString(), "ether")) { + let allowance = await polyToken.methods.allowance(Issuer.address, spender).call(); + if (parseInt(allowance) >= parseInt(requiredAmount)) { return true; } else { - let approveAction = polyToken.methods.approve(spender, web3.utils.toWei(fee.toString(), "ether")); - await common.sendTransaction(Issuer, approveAction, defaultGasPrice); + let approveAction = polyToken.methods.approve(spender, requiredAmount); + await common.sendTransaction(approveAction); } } else { - let requiredBalance = parseInt(requiredAmount) - parseInt(polyBalance); - console.log(chalk.red(`\n*****************************************************************************************************************************************`)); - console.log(chalk.red(`Not enough balance to Pay the Fee, Require ${(new BigNumber(requiredBalance).dividedBy(new BigNumber(10).pow(18))).toNumber()} POLY but have ${(new BigNumber(polyBalance).dividedBy(new BigNumber(10).pow(18))).toNumber()} POLY. Access POLY faucet to get the POLY to complete this txn`)); - console.log(chalk.red(`******************************************************************************************************************************************\n`)); - process.exit(0); + let requiredBalance = parseInt(requiredAmount) - parseInt(polyBalance); + console.log(chalk.red(`\n*****************************************************************************************************************************************`)); + console.log(chalk.red(`Not enough balance to Pay the Fee, Require ${(new BigNumber(requiredBalance).dividedBy(new BigNumber(10).pow(18))).toNumber()} POLY but have ${(new BigNumber(polyBalance).dividedBy(new BigNumber(10).pow(18))).toNumber()} POLY. Access POLY faucet to get the POLY to complete this txn`)); + console.log(chalk.red(`******************************************************************************************************************************************\n`)); + process.exit(0); } } module.exports = { - executeApp: async function(tokenConfig, mintingConfig, stoConfig, remoteNetwork) { - return executeApp(tokenConfig, mintingConfig, stoConfig, remoteNetwork); + executeApp: async function (ticker, transferOwnership, name, details, divisible) { + return executeApp(ticker, transferOwnership, name, details, divisible); } } diff --git a/CLI/commands/TickerRollForward.js b/CLI/commands/TickerRollForward.js index 97bd1f519..266749da1 100644 --- a/CLI/commands/TickerRollForward.js +++ b/CLI/commands/TickerRollForward.js @@ -3,15 +3,11 @@ var csv = require('fast-csv'); var BigNumber = require('bignumber.js'); var chalk = require('chalk'); var common = require('./common/common_functions'); -var global = require('./common/global'); ///////////////////////// ARTIFACTS ///////////////////////// var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis'); -////////////////////////////USER INPUTS////////////////////////////////////////// -let remoteNetwork = process.argv.slice(2)[0]; //batch size - ///////////////////////// GLOBAL VARS ///////////////////////// let ticker_data = []; let registered_tickers = []; @@ -24,9 +20,9 @@ let securityTokenRegistry; let securityTokenRegistryAddress; function Ticker(_owner, _symbol, _name) { - this.owner = _owner; - this.symbol = _symbol; - this.name = _name; + this.owner = _owner; + this.symbol = _symbol; + this.name = _name; } function FailedRegistration(_ticker, _error) { @@ -47,7 +43,6 @@ function FailedRegistration(_ticker, _error) { startScript(); async function startScript() { - await global.initialize(remoteNetwork); securityTokenRegistryAddress = await contracts.securityTokenRegistry(); let securityTokenRegistryABI = abis.securityTokenRegistry(); @@ -63,11 +58,11 @@ async function startScript() { } async function readFile() { - var stream = fs.createReadStream("./CLI/data/ticker_data.csv"); + var stream = fs.createReadStream(`${__dirname}/../data/ticker_data.csv`); var csvStream = csv() .on("data", function (data) { - ticker_data.push(new Ticker(data[0],data[1],data[2],data[3])); + ticker_data.push(new Ticker(data[0], data[1], data[2], data[3])); }) .on("end", async function () { await registerTickers(); @@ -78,17 +73,17 @@ async function readFile() { async function registerTickers() { // Poly approval for registration fees let polyBalance = BigNumber(await polyToken.methods.balanceOf(Issuer.address).call()); - let fee = web3.utils.fromWei(await securityTokenRegistry.methods.getTickerRegistrationFee().call()); + let fee = web3.utils.fromWei(await securityTokenRegistry.methods.getTickerRegistrationFee().call()); let totalFee = BigNumber(ticker_data.length).mul(fee); if (totalFee.gt(polyBalance)) { console.log(chalk.red(`\n*******************************************************************************`)); - console.log(chalk.red(`Not enough POLY to pay registration fee. Require ${totalFee.div(10**18).toNumber()} POLY but have ${polyBalance.div(10**18).toNumber()} POLY.`)); + console.log(chalk.red(`Not enough POLY to pay registration fee. Require ${totalFee.div(10 ** 18).toNumber()} POLY but have ${polyBalance.div(10 ** 18).toNumber()} POLY.`)); console.log(chalk.red(`*******************************************************************************\n`)); process.exit(0); } else { let approveAction = polyToken.methods.approve(securityTokenRegistryAddress, totalFee); - let receipt = await common.sendTransaction(Issuer, approveAction, defaultGasPrice); + let receipt = await common.sendTransaction(approveAction); totalGas = totalGas.add(receipt.gasUsed); } @@ -105,7 +100,7 @@ async function registerTickers() { } // validate ticker - await securityTokenRegistry.methods.getTickerDetails(ticker_data[i].symbol).call({}, function(error, result){ + await securityTokenRegistry.methods.getTickerDetails(ticker_data[i].symbol).call({}, function (error, result) { if (result[1] != 0) { failed_tickers.push(` ${i} is already registered`); valid = false; @@ -115,7 +110,7 @@ async function registerTickers() { if (valid) { try { let registerTickerAction = securityTokenRegistry.methods.registerTicker(owner, ticker_data[i].symbol, ticker_data[i].name); - let receipt = await common.sendTransaction(Issuer, registerTickerAction, defaultGasPrice); + let receipt = await common.sendTransaction(registerTickerAction); registered_tickers.push(ticker_data[i]); console.log(ticker_data[i]); totalGas = totalGas.add(receipt.gasUsed); @@ -136,7 +131,7 @@ async function logResults() { Successful registrations: ${registered_tickers.length} Failed registrations: ${failed_tickers.length} Total gas consumed: ${totalGas} - Total gas cost: ${defaultGasPrice.mul(totalGas).div(10**18)} ETH + Total gas cost: ${defaultGasPrice.mul(totalGas).div(10 ** 18)} ETH List of failed registrations: ${failed_tickers} diff --git a/CLI/commands/accredit.js b/CLI/commands/accredit.js deleted file mode 100644 index c4dbd78b6..000000000 --- a/CLI/commands/accredit.js +++ /dev/null @@ -1,148 +0,0 @@ -var fs = require('fs'); -var csv = require('fast-csv'); -var BigNumber = require('bignumber.js'); -var chalk = require('chalk'); -var common = require('./common/common_functions'); -var global = require('./common/global'); -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis') - -/////////////////////////////ARTIFACTS////////////////////////////////////////// -let securityTokenRegistry; -let securityToken; -let usdTieredSTO; - -////////////////////////////USER INPUTS////////////////////////////////////////// -let tokenSymbol = process.argv.slice(2)[0]; //token symbol -let BATCH_SIZE = process.argv.slice(2)[1]; //batch size -if (!BATCH_SIZE) BATCH_SIZE = 75; -let remoteNetwork = process.argv.slice(2)[2]; - -/////////////////////////GLOBAL VARS////////////////////////////////////////// -//distribData is an array of batches. i.e. if there are 200 entries, with batch sizes of 75, we get [[75],[75],[50]] -let distribData = new Array(); -//allocData is a temporary array that stores up to the batch size, -//then gets push into distribData, then gets set to 0 to start another batch -let allocData = new Array(); -//full file data is a single array that contains all arrays. i.e. if there are 200 entries we get [[200]] -let fullFileData = new Array(); -//baa data is an array that contains invalid entries -let badData = new Array(); - -//////////////////////////////////////////ENTRY INTO SCRIPT////////////////////////////////////////// -startScript(); - -async function startScript() { - if (remoteNetwork == 'undefined') remoteNetwork = undefined; - await global.initialize(remoteNetwork); - try { - let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - securityTokenRegistry.setProvider(web3.currentProvider); - console.log("Processing investor CSV upload. Batch size is " + BATCH_SIZE + " accounts per transaction"); - readFile(); - } catch (err) { - console.log(err) - console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); - return; - } -} - -///////////////////////////FUNCTION READING THE CSV FILE -function readFile() { - var stream = fs.createReadStream("./CLI/data/accredited_data.csv"); - - let index = 0; - console.log(` - -------------------------------------------- - ----------- Parsing the csv file ----------- - -------------------------------------------- - `); - - var csvStream = csv() - .on("data", function (data) { - let isAddress = web3.utils.isAddress(data[0]); - let isAccredited = (typeof JSON.parse(data[1].toLowerCase())) == "boolean" ? JSON.parse(data[1].toLowerCase()) : "not-valid"; - - if (isAddress && (isAccredited != "not-valid") ) { - let userArray = new Array() - let checksummedAddress = web3.utils.toChecksumAddress(data[0]); - - userArray.push(checksummedAddress) - userArray.push(isAccredited) - - allocData.push(userArray); - fullFileData.push(userArray); - - index++; - if (index >= BATCH_SIZE) { - distribData.push(allocData); - allocData = []; - index = 0; - } - } else { - let userArray = new Array() - userArray.push(data[0]) - userArray.push(isAccredited); - - badData.push(userArray); - fullFileData.push(userArray) - } - }) - .on("end", function () { - //Add last remainder batch - distribData.push(allocData); - allocData = []; - - changeAccredited(); - }); - - stream.pipe(csvStream); -} - -// MAIN FUNCTION COMMUNICATING TO BLOCKCHAIN -async function changeAccredited() { - // Let's check if token has already been deployed, if it has, skip to STO - let tokenDeployedAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (tokenDeployedAddress != "0x0000000000000000000000000000000000000000") { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, tokenDeployedAddress); - let result = await securityToken.methods.getModulesByName(web3.utils.toHex('USDTieredSTO')).call(); - if (result.length > 0) { - let usdTieredSTOABI = abis.usdTieredSTO(); - usdTieredSTO = new web3.eth.Contract(usdTieredSTOABI, result[0]); - console.log(` -------------------------------------------------------- ------ Sending accreditation changes to blockchain ----- -------------------------------------------------------- - `); - //this for loop will do the batches, so it should run 75, 75, 50 with 200 - for (let i = 0; i < distribData.length; i++) { - try { - let investorArray = []; - let isAccreditedArray = []; - - //splitting the user arrays to be organized by input - for (let j = 0; j < distribData[i].length; j++) { - investorArray.push(distribData[i][j][0]) - isAccreditedArray.push(distribData[i][j][1]) - } - - let changeAccreditedAction = usdTieredSTO.methods.changeAccredited(investorArray, isAccreditedArray); - let r = await common.sendTransaction(Issuer, changeAccreditedAction, defaultGasPrice); - console.log(`Batch ${i} - Attempting to change accredited accounts:\n\n`, investorArray, "\n\n"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------"); - console.log("Change accredited transaction was successful.", r.gasUsed, "gas used. Spent:", web3.utils.fromWei(BigNumber(r.gasUsed * defaultGasPrice).toString(), "ether"), "Ether"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n\n"); - } catch (err) { - console.log("ERROR:", err); - } - } - } else { - console.log(chalk.red(`There is no USDTieredSTO module attached to the ${tokenSymbol.toUpperCase()} Token. No further actions can be taken.`)); - } - } else { - console.log(chalk.red(`Token symbol provided is not a registered Security Token.`)); - } -} diff --git a/CLI/commands/changeNonAccreditedLimit.js b/CLI/commands/changeNonAccreditedLimit.js deleted file mode 100644 index b1ececbc7..000000000 --- a/CLI/commands/changeNonAccreditedLimit.js +++ /dev/null @@ -1,148 +0,0 @@ -var fs = require('fs'); -var csv = require('fast-csv'); -var BigNumber = require('bignumber.js'); -var chalk = require('chalk'); -var common = require('./common/common_functions'); -var global = require('./common/global'); -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis') - -/////////////////////////////ARTIFACTS////////////////////////////////////////// -let securityTokenRegistry; -let securityToken; -let usdTieredSTO; - -////////////////////////////USER INPUTS////////////////////////////////////////// -let tokenSymbol = process.argv.slice(2)[0]; //token symbol -let BATCH_SIZE = process.argv.slice(2)[1]; //batch size -if (!BATCH_SIZE) BATCH_SIZE = 75; -let remoteNetwork = process.argv.slice(2)[2]; - -/////////////////////////GLOBAL VARS////////////////////////////////////////// -//distribData is an array of batches. i.e. if there are 200 entries, with batch sizes of 75, we get [[75],[75],[50]] -let distribData = new Array(); -//allocData is a temporary array that stores up to the batch size, -//then gets push into distribData, then gets set to 0 to start another batch -let allocData = new Array(); -//full file data is a single array that contains all arrays. i.e. if there are 200 entries we get [[200]] -let fullFileData = new Array(); -//baa data is an array that contains invalid entries -let badData = new Array(); - -//////////////////////////////////////////ENTRY INTO SCRIPT////////////////////////////////////////// -startScript(); - -async function startScript() { - if (remoteNetwork == 'undefined') remoteNetwork = undefined; - await global.initialize(remoteNetwork); - try { - let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - securityTokenRegistry.setProvider(web3.currentProvider); - console.log("Processing investor CSV upload. Batch size is " + BATCH_SIZE + " accounts per transaction"); - readFile(); - } catch (err) { - console.log(err) - console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); - return; - } -} - -///////////////////////////FUNCTION READING THE CSV FILE -function readFile() { - var stream = fs.createReadStream("./CLI/data/nonAccreditedLimits_data.csv"); - - let index = 0; - console.log(` - -------------------------------------------- - ----------- Parsing the csv file ----------- - -------------------------------------------- - `); - - var csvStream = csv() - .on("data", function (data) { - let isAddress = web3.utils.isAddress(data[0]); - let isNumber = !isNaN(data[1]); - - if (isAddress && isNumber) { - let userArray = new Array(); - let checksummedAddress = web3.utils.toChecksumAddress(data[0]); - - userArray.push(checksummedAddress); - userArray.push(data[1]); - - allocData.push(userArray); - fullFileData.push(userArray); - - index++; - if (index >= BATCH_SIZE) { - distribData.push(allocData); - allocData = []; - index = 0; - } - } else { - let userArray = new Array() - userArray.push(data[0]); - userArray.push(data[1]); - - badData.push(userArray); - fullFileData.push(userArray) - } - }) - .on("end", function () { - //Add last remainder batch - distribData.push(allocData); - allocData = []; - - changeNonAccreditedLimit(); - }); - - stream.pipe(csvStream); -} - -// MAIN FUNCTION COMMUNICATING TO BLOCKCHAIN -async function changeNonAccreditedLimit() { - // Let's check if token has already been deployed, if it has, skip to STO - let tokenDeployedAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (tokenDeployedAddress != "0x0000000000000000000000000000000000000000") { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, tokenDeployedAddress); - let result = await securityToken.methods.getModulesByName(web3.utils.toHex('USDTieredSTO')).call(); - if (result.length > 0) { - let usdTieredSTOABI = abis.usdTieredSTO(); - usdTieredSTO = new web3.eth.Contract(usdTieredSTOABI, result[0]); - console.log(` --------------------------------------------------------------- ------ Sending non accredited limit changes to blockchain ----- --------------------------------------------------------------- - `); - //this for loop will do the batches, so it should run 75, 75, 50 with 200 - for (let i = 0; i < distribData.length; i++) { - try { - let investorArray = []; - let limitArray = []; - - //splitting the user arrays to be organized by input - for (let j = 0; j < distribData[i].length; j++) { - investorArray.push(distribData[i][j][0]); - limitArray.push(web3.utils.toWei(distribData[i][j][1].toString())); - } - - let changeNonAccreditedLimitAction = usdTieredSTO.methods.changeNonAccreditedLimit(investorArray, limitArray); - let r = await common.sendTransaction(Issuer, changeNonAccreditedLimitAction, defaultGasPrice); - console.log(`Batch ${i} - Attempting to change non accredited limits to accounts:\n\n`, investorArray, "\n\n"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------"); - console.log("Change accredited transaction was successful.", r.gasUsed, "gas used. Spent:", web3.utils.fromWei(BigNumber(r.gasUsed * defaultGasPrice).toString()), "Ether"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n\n"); - } catch (err) { - console.log("ERROR:", err); - } - } - } else { - console.log(chalk.red(`There is no STO module attached to the ${tokenSymbol.toUpperCase()} Token. No further actions can be taken.`)); - } - } else { - console.log(chalk.red(`Token symbol provided is not a registered Security Token.`)); - } -} diff --git a/CLI/commands/common/common_functions.js b/CLI/commands/common/common_functions.js index 05974bb66..40665d514 100644 --- a/CLI/commands/common/common_functions.js +++ b/CLI/commands/common/common_functions.js @@ -3,7 +3,7 @@ const Tx = require('ethereumjs-tx'); const permissionsList = require('./permissions_list'); const abis = require('../helpers/contract_abis'); -async function connect(abi, address) { +function connect(abi, address) { contractRegistry = new web3.eth.Contract(abi, address); contractRegistry.setProvider(web3.currentProvider); return contractRegistry @@ -15,176 +15,133 @@ async function checkPermission(contractName, functionName, contractRegistry) { return true } else { let stAddress = await contractRegistry.methods.securityToken().call(); - let securityToken = await connect(abis.securityToken(), stAddress); + let securityToken = connect(abis.securityToken(), stAddress); let stOwner = await securityToken.methods.owner().call(); if (stOwner == Issuer.address) { return true + } else { + let result = await securityToken.methods.checkPermission(Issuer.address, contractRegistry.options.address, web3.utils.asciiToHex(permission)).call(); + return result } - let result = await securityToken.methods.checkPermission(Issuer.address, contractRegistry.options.address, web3.utils.asciiToHex(permission)).call(); - return result } }; +function getFinalOptions(options) { + if (typeof options != "object") { + options = {} + } + const defaultOptions = { + from: Issuer, + gasPrice: defaultGasPrice, + value: undefined, + factor: 1.2, + minNonce: 0 + } + return Object.assign(defaultOptions, options) +}; + +async function getGasLimit(options, action) { + let block = await web3.eth.getBlock('latest'); + let networkGasLimit = block.gasLimit; + let gas = Math.round(options.factor * (await action.estimateGas({ from: options.from.address, value: options.value }))); + return (gas > networkGasLimit) ? networkGasLimit : gas; +} + +async function checkPermissions(action) { + let contractRegistry = connect(action._parent.options.jsonInterface, action._parent._address); + //NOTE this is a condition to verify if the transaction comes from a module or not. + if (contractRegistry.methods.hasOwnProperty('factory')) { + let moduleAddress = await contractRegistry.methods.factory().call(); + let moduleRegistry = connect(abis.moduleFactory(), moduleAddress); + let parentModule = await moduleRegistry.methods.getName().call(); + let result = await checkPermission(web3.utils.hexToUtf8(parentModule), action._method.name, contractRegistry); + if (!result) { + console.log("You haven't the right permissions to execute this method."); + process.exit(0); + } + } + return +} + module.exports = { convertToDaysRemaining: function (timeRemaining) { var seconds = parseInt(timeRemaining, 10); - + var days = Math.floor(seconds / (3600 * 24)); - seconds -= days * 3600 * 24; - var hrs = Math.floor(seconds / 3600); - seconds -= hrs * 3600; + seconds -= days * 3600 * 24; + var hrs = Math.floor(seconds / 3600); + seconds -= hrs * 3600; var mnts = Math.floor(seconds / 60); - seconds -= mnts * 60; + seconds -= mnts * 60; return (days + " days, " + hrs + " Hrs, " + mnts + " Minutes, " + seconds + " Seconds"); }, - logAsciiBull: function() { - console.log(` -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(@(&&@@@@@@@@@@@@@@@@@@@@@@@@@@(((@&&&&(/@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(#(((((((#%%%#@@@@@@@@@@@@@@@@@@@@%##(((/@@@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(%(((((((((((#%%%%%@#@@@@@@@@@@@@(&#####@@@@@@@@%& -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&#((((((((((((((##%%%%%%%&&&%%##@%#####%(@@@@@@@#%#& -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(%((((((((((((((((((###%%%%%((#####%%%####@@@@@@@###((@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(#(((((((((((((((((((((####%%%#((((######%&%@@(##&###(@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(#((((((((((((((((((((((((####%%#(((((((#((((((((((((#(@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(%(((((((((((((((((((((((((((######%(((((((((((((#&(/@@@@@@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&#(((((((((((((((((((((((((((((((###############(##%%#@@@@@@@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@(#((((##############(((((((((((((((((###################%@@@@@@@@@@ -@@@@@@@@@@@@@@@@@@@@@@@@@@@(&#((#(##################((((((((((((((((((##%%##############@@@@@@@@@@@ -@@@@@@@@@@@@@@@@@@@@@/%#(((((((##%((((##############((((((((((((((((((##%%#############%%@@@@@@@@@@ -@@@@@@@@@@@@@@@@@@@@((((((((((###%%((((((##########(((((((((((((((((((#%%%############%%%#@@@@@@@@@ -@@@@@@@@@@@@@@@@@@%((((((((((####%%%((((((((#######(((((((((((####(((((@%%############%%%#@@@@@@@@@ -@@@@@@@@@####%%%%%#(((((((((#####%%%%(((((((((((###((((#######(((((((((&@@(&#########%%%%&@@@@@@@@@ -@@@@@@@@&(((#####%###(((((((#####%%%%%((((((((####%%%%%%%%&%@%#((((((((@@@@@@(#(####%%%%%%@@@@@@@@@ -@@@@@@@&(((@@@@@@@####(((((######%%%%%%##&########%%%%%#@@@@###(((((#(@@@@@@@@@@@###%%#@@@@@@@@@@@@ -@@@#%&%(((@@@@@@@@#####(((#######%%%%@@@@@@@@@@@((##@@@@@@@@%###((((/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@#%%&%#@@@@@@@@@@############%%%%@@@@@@@@@@@@@@@@@@@@(@&&&&#####(#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@@@#%%%%%#((%%%%%%#@@@@@@@@@@@@@@@@@@@@(####%((((%#(@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@@@&%%%#((((((%%&@@@@@@@@@@@@@@@@@@@@@@###%%#((@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@@%%%%((((((((& @@@@@@@@@@@@@@@@@@@@@@@%%&%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@@%%(((((&#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@&((###@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@@#####@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@@&####@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@&&%##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@&&&%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@@@@%##%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -@@@@@@@@@@#%####%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + logAsciiBull: function () { + console.log(` + /######%%, /#( + ##########%%%%%, ,%%%. % + *#############%%%%%##%%%%%%# ## + (################%%%%#####%%%%//###%, + .####################%%%%#########/ + (#########%%############%%%%%%%%%#%%% + ,(%#%%%%%%%%%%%%############%%%%%%%###%%%. + (######%%###%%%%%%%%##############%%%%%####%%%* + /#######%%%%######%%%%##########%###,.%######%%%( + #%%%%%#######%%%%%%###########%%%%%*###### /####%%%# + #. ,%%####%%%%%%%(/#%%%%%%%%( #%#### ,#%/ + *#%( .%%%##%%%%%% .%%%#* + .%%%%#%%%% .%%%###( + %%%#####% (%%. + #%###(, + *#%# + %%# + * + &% + %%%. `); }, - getNonce: async function(from) { + getNonce: async function (from) { return (await web3.eth.getTransactionCount(from.address, "pending")); }, - sendTransaction: async function (from, action, gasPrice, value, factor) { - let contractRegistry = await connect(action._parent.options.jsonInterface, action._parent._address); - - //NOTE this is a condition to verify if the transaction comes from a module or not. - if (contractRegistry.methods.hasOwnProperty('factory')) { - let moduleAddress = await contractRegistry.methods.factory().call(); - let moduleRegistry = await connect(abis.moduleFactory(), moduleAddress) - let parentModule = await moduleRegistry.methods.getName().call(); - let result = await checkPermission(web3.utils.hexToUtf8(parentModule), action._method.name, contractRegistry); - if (!result) { - console.log("You haven't the right permissions to execute this method."); - process.exit(0); - } - } + sendTransaction: async function (action, options) { + await checkPermissions(action); - if (typeof factor === 'undefined') factor = 1.2; + options = getFinalOptions(options); + let gasLimit = await getGasLimit(options, action); - let block = await web3.eth.getBlock("latest"); - let networkGasLimit = block.gasLimit; - let gas = Math.round(factor * (await action.estimateGas({ from: from.address, value: value}))); - if (gas > networkGasLimit) gas = networkGasLimit; - - console.log(chalk.black.bgYellowBright(`---- Transaction executed: ${action._method.name} - Gas limit provided: ${gas} ----`)); + console.log(chalk.black.bgYellowBright(`---- Transaction executed: ${action._method.name} - Gas limit provided: ${gasLimit} ----`)); - let nonce = await web3.eth.getTransactionCount(from.address); - let abi = action.encodeABI(); - let parameter = { - from: from.address, - to: action._parent._address, - data: abi, - gasLimit: gas, - gasPrice: gasPrice, - nonce: nonce, - value: web3.utils.toHex(value) - }; - - const transaction = new Tx(parameter); - transaction.sign(Buffer.from(from.privateKey.replace('0x', ''), 'hex')); - return await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex')) - .on('transactionHash', function(hash){ - console.log(` - Your transaction is being processed. Please wait... - TxHash: ${hash}` - ); - }) - .on('receipt', function(receipt){ - console.log(` - Congratulations! The transaction was successfully completed. - Gas used: ${receipt.gasUsed} - Gas spent: ${web3.utils.fromWei((new web3.utils.BN(gasPrice)).mul(new web3.utils.BN(receipt.gasUsed)))} Ether - Review it on Etherscan. - TxHash: ${receipt.transactionHash}\n` - ); - }); - }, - sendTransactionWithNonce: async function (from, action, gasPrice, minNonce, value, factor) { - let contractRegistry = await connect(action._parent.options.jsonInterface, action._parent._address); - - //NOTE this is a condition to verify if the transaction comes from a module or not. - if (contractRegistry.methods.hasOwnProperty('factory')) { - let moduleAddress = await contractRegistry.methods.factory().call(); - let moduleRegistry = await connect(abis.moduleFactory(), moduleAddress) - let parentModule = await moduleRegistry.methods.getName().call(); - let result = await checkPermission(web3.utils.hexToUtf8(parentModule), action._method.name, contractRegistry); - if (!result) { - console.log("You haven't the right permissions to execute this method."); - process.exit(0); - } - } - if (typeof factor === 'undefined') factor = 1.2; - - let block = await web3.eth.getBlock("latest"); - let networkGasLimit = block.gasLimit; - - let gas = Math.round(factor * (await action.estimateGas({ from: from.address, value: value}))); - if (gas > networkGasLimit) gas = networkGasLimit; - - console.log(chalk.black.bgYellowBright(`---- Transaction executed: ${action._method.name} - Gas limit provided: ${gas} ----`)); - - let nonce = await web3.eth.getTransactionCount(from.address); - - if (nonce < minNonce) { + let nonce = await web3.eth.getTransactionCount(options.from.address); + if (nonce < options.minNonce) { nonce = minNonce; } let abi = action.encodeABI(); let parameter = { - from: from.address, + from: options.from.address, to: action._parent._address, data: abi, - gasLimit: gas, - gasPrice: gasPrice, + gasLimit: gasLimit, + gasPrice: options.gasPrice, nonce: nonce, - value: web3.utils.toHex(value) + value: web3.utils.toHex(options.value) }; - + const transaction = new Tx(parameter); - transaction.sign(Buffer.from(from.privateKey.replace('0x', ''), 'hex')); + transaction.sign(Buffer.from(options.from.privateKey.replace('0x', ''), 'hex')); return await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex')) - .on('transactionHash', function(hash){ - console.log(` + .on('transactionHash', function (hash) { + console.log(` Your transaction is being processed. Please wait... TxHash: ${hash}` - ); - }) - .on('receipt', function(receipt){ - console.log(` + ); + }) + .on('receipt', function (receipt) { + console.log(` Congratulations! The transaction was successfully completed. - Gas used: ${receipt.gasUsed} - Gas spent: ${web3.utils.fromWei((new web3.utils.BN(gasPrice)).mul(new web3.utils.BN(receipt.gasUsed)))} Ether + Gas used: ${receipt.gasUsed} - Gas spent: ${web3.utils.fromWei((new web3.utils.BN(options.gasPrice)).mul(new web3.utils.BN(receipt.gasUsed)))} Ether Review it on Etherscan. TxHash: ${receipt.transactionHash}\n` - ); - }); + ); + }); }, getEventFromLogs: function (jsonInterface, logs, eventName) { let eventJsonInterface = jsonInterface.find(o => o.name === eventName && o.type === 'event'); @@ -195,5 +152,25 @@ module.exports = { let eventJsonInterface = jsonInterface.find(o => o.name === eventName && o.type === 'event'); let filteredLogs = logs.filter(l => l.topics.includes(eventJsonInterface.signature)); return filteredLogs.map(l => web3.eth.abi.decodeLog(eventJsonInterface.inputs, l.data, l.topics.slice(1))); + }, + connect: function (abi, address) { + return connect(abi, address) + }, + splitIntoBatches: function (data, batchSize) { + let allBatches = []; + for (let index = 0; index < data.length; index += parseInt(batchSize)) { + allBatches.push(data.slice(index, index + parseInt(batchSize))); + } + return allBatches; + }, + transposeBatches: function (batches) { + let result = []; + if (batches.length > 0 && batches[0].length > 0) { + let columns = batches[0][0].length; + for (let index = 0; index < columns; index++) { + result[index] = batches.map(batch => batch.map(record => record[index])); + } + } + return result; } }; diff --git a/CLI/commands/common/constants.js b/CLI/commands/common/constants.js new file mode 100644 index 000000000..d121fd2da --- /dev/null +++ b/CLI/commands/common/constants.js @@ -0,0 +1,36 @@ +module.exports = Object.freeze({ + MODULES_TYPES: { + PERMISSION: 1, + TRANSFER: 2, + STO: 3, + DIVIDENDS: 4, + BURN: 5 + }, + DURATION: { + seconds: function (val) { + return val + }, + minutes: function (val) { + return val * this.seconds(60) + }, + hours: function (val) { + return val * this.minutes(60) + }, + days: function (val) { + return val * this.hours(24) + }, + weeks: function (val) { + return val * this.days(7) + }, + years: function (val) { + return val * this.days(365) + } + }, + FUND_RAISE_TYPES: { + ETH: 0, + POLY: 1, + STABLE: 2 + }, + DEFAULT_BATCH_SIZE: 75, + ADDRESS_ZERO: '0x0000000000000000000000000000000000000000' +}); \ No newline at end of file diff --git a/CLI/commands/common/global.js b/CLI/commands/common/global.js index 1285766df..4b8fc5a3a 100644 --- a/CLI/commands/common/global.js +++ b/CLI/commands/common/global.js @@ -1,39 +1,55 @@ const Web3 = require('web3'); +const constants = require('./constants'); -function getGasPrice (networkId) { - let gasPrice; - switch (networkId) { - case 1: //Mainnet - gasPrice = 20000000000; - break; - case 3: //Ropsten - gasPrice = 50000000000; - break; - case 15: //Ganache - gasPrice = 50000000000; - break; - case 42: //Kovan - gasPrice = 5000000000; - break; - default: - throw new Error('Network ID not identified'); - } +global.web3, global.Issuer, global.defaultGasPrice, global.remoteNetwork; + +function getGasPrice(networkId) { + let gasPrice; + switch (networkId) { + case 1: //Mainnet + gasPrice = 4000000000; + break; + case 3: //Ropsten + gasPrice = 50000000000; + break; + case 15: //Ganache + gasPrice = 50000000000; + break; + case 42: //Kovan + gasPrice = 50000000000; + break; + default: + throw new Error('Network ID not identified'); + } + return gasPrice; +} - return gasPrice; +function providerValidator(url) { + var expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g; + var regex = new RegExp(expression); + return url.match(regex); } + +async function httpProvider(url, file) { + web3 = new Web3(new Web3.providers.HttpProvider(url)); + Issuer = await web3.eth.accounts.privateKeyToAccount("0x" + require('fs').readFileSync(file).toString()); +} + module.exports = { - initialize: async function (remoteNetwork) { - if (typeof web3 === 'undefined' || typeof Issuer === 'undefined' || typeof defaultGasPrice === 'undefined') { - if (typeof remoteNetwork !== 'undefined') { - web3 = new Web3(new Web3.providers.HttpProvider(`https://${remoteNetwork}.infura.io/`)); - let privKey = require('fs').readFileSync('./privKey').toString(); - Issuer = await web3.eth.accounts.privateKeyToAccount("0x" + privKey); - } else { - web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); - let privKeyLocal = require('fs').readFileSync('./privKeyLocal').toString() - Issuer = await web3.eth.accounts.privateKeyToAccount("0x" + privKeyLocal); + initialize: async function (network) { + remoteNetwork = network; + if (typeof web3 === 'undefined' || typeof Issuer === 'undefined' || typeof defaultGasPrice === 'undefined') { + if (typeof remoteNetwork !== 'undefined') { + if (!providerValidator(remoteNetwork)) { + console.log("Invalid remote node") + process.exit(0) } - defaultGasPrice = getGasPrice(await web3.eth.net.getId()); + await httpProvider(remoteNetwork, `${__dirname}/../../../privKey`); + } else { + await httpProvider("http://localhost:8545", `${__dirname}/../../../privKeyLocal`); } + defaultGasPrice = getGasPrice(await web3.eth.net.getId()); } + }, + constants }; \ No newline at end of file diff --git a/CLI/commands/common/permissions_list.js b/CLI/commands/common/permissions_list.js index 4d2b58cf7..8f74c15e7 100644 --- a/CLI/commands/common/permissions_list.js +++ b/CLI/commands/common/permissions_list.js @@ -60,9 +60,7 @@ function getPermissionList() { }, ManualApprovalTransferManager: { addManualApproval: "TRANSFER_APPROVAL", - addManualBlocking: "TRANSFER_APPROVAL", revokeManualApproval: "TRANSFER_APPROVAL", - revokeManualBlocking: "TRANSFER_APPROVAL" }, PercentageTransferManager: { modifyWhitelist: "WHITELIST", @@ -70,36 +68,60 @@ function getPermissionList() { setAllowPrimaryIssuance: "ADMIN", changeHolderPercentage: "ADMIN" }, - LockupVolumeRestrictionTM: { - addLockup: "ADMIN", - addLockUpMulti: "ADMIN", - removeLockUp: "ADMIN", - modifyLockUp: "ADMIN" + VolumeRestrictionTM: { + changeExemptWalletList: "ADMIN", + addIndividualRestriction: "ADMIN", + addIndividualRestrictionMulti: "ADMIN", + addGlobalRestriction: "ADMIN", + addDailyGlobalRestriction: "ADMIN", + removeIndividualRestriction: "ADMIN", + removeIndividualRestrictionMulti: "ADMIN", + removeGlobalRestriction: "ADMIN", + removeDailyGlobalRestriction: "ADMIN", + modifyIndividualRestriction: "ADMIN", + modifyIndividualRestrictionMulti: "ADMIN", + modifyGlobalRestriction: "ADMIN", + modifyDailyGlobalRestriction: "ADMIN" }, - SingleTradeVolumeRestrictionTM: { - setAllowPrimaryIssuance: "ADMIN", - changeTransferLimitToPercentage: "ADMIN", - changeTransferLimitToTokens: "ADMIN", - changeGlobalLimitInTokens: "ADMIN", - changeGlobalLimitInPercentage: "ADMIN", - addExemptWallet: "ADMIN", - removeExemptWallet: "ADMIN", - addExemptWalletMulti: "ADMIN", - removeExemptWalletMulti: "ADMIN", - setTransferLimitInTokens: "ADMIN", - setTransferLimitInPercentage: "ADMIN", - removeTransferLimitInPercentage: "ADMIN", - removeTransferLimitInTokens: "ADMIN", - setTransferLimitInTokensMulti: "ADMIN", - setTransferLimitInPercentageMulti: "ADMIN", - removeTransferLimitInTokensMulti: "ADMIN", - removeTransferLimitInPercentageMulti: "ADMIN" + BlacklistTransferManager: { + addBlacklistType: "ADMIN", + addBlacklistTypeMulti: "ADMIN", + modifyBlacklistType: "ADMIN", + modifyBlacklistTypeMulti: "ADMIN", + deleteBlacklistType: "ADMIN", + deleteBlacklistTypeMulti: "ADMIN", + addInvestorToBlacklist: "ADMIN", + addInvestorToBlacklistMulti: "ADMIN", + addMultiInvestorToBlacklistMulti: "ADMIN", + addInvestorToNewBlacklist: "ADMIN", + deleteInvestorFromAllBlacklist: "ADMIN", + deleteInvestorFromAllBlacklistMulti: "ADMIN", + deleteInvestorFromBlacklist: "ADMIN", + deleteMultiInvestorsFromBlacklistMulti: "ADMIN", + }, + VestingEscrowWallet: { + changeTreasuryWallet: "ONLY_OWNER", + depositTokens: "ADMIN", + sendToTreasury: "ADMIN", + pushAvailableTokens: "ADMIN", + addTemplate: "ADMIN", + removeTemplate: "ADMIN", + addSchedule: "ADMIN", + addScheduleFromTemplate: "ADMIN", + modifySchedule: "ADMIN", + revokeSchedule: "ADMIN", + revokeAllSchedules: "ADMIN", + pushAvailableTokensMulti: "ADMIN", + addScheduleMulti: "ADMIN", + addScheduleFromTemplateMulti: "ADMIN", + revokeSchedulesMulti: "ADMIN", + modifyScheduleMulti: "ADMIN" } } } module.exports = { - verifyPermission: function(contractName, functionName) { + verifyPermission: function (contractName, functionName) { let list = getPermissionList(); try { return list[contractName][functionName] diff --git a/CLI/commands/contract_manager.js b/CLI/commands/contract_manager.js index bea83ce7d..cce93444e 100644 --- a/CLI/commands/contract_manager.js +++ b/CLI/commands/contract_manager.js @@ -1,23 +1,14 @@ -const duration = { - seconds: function (val) { return val; }, - minutes: function (val) { return val * this.seconds(60); }, - hours: function (val) { return val * this.minutes(60); }, - days: function (val) { return val * this.hours(24); }, - weeks: function (val) { return val * this.days(7); }, - years: function (val) { return val * this.days(365); }, -}; var readlineSync = require('readline-sync'); var chalk = require('chalk'); var common = require('./common/common_functions'); -var global = require('./common/global'); +var gbl = require('./common/global'); var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis'); // App flow let currentContract = null; -async function executeApp(remoteNetwork) { - await global.initialize(remoteNetwork); +async function executeApp() { common.logAsciiBull(); console.log("*********************************************"); @@ -108,7 +99,7 @@ async function strActions() { let tickerExpiryDate = readlineSync.question(`Enter the Unix Epoch time on wich the ticker will expire: `); let tickerStatus = readlineSync.keyInYNStrict(`Is the token deployed?`); let modifyTickerAction = currentContract.methods.modifyTicker(tickerOwner, tickerToModify, tickerSTName, tickerRegistrationDate, tickerExpiryDate, tickerStatus); - await common.sendTransaction(Issuer, modifyTickerAction, defaultGasPrice, 0, 1.5); + await common.sendTransaction(modifyTickerAction, {factor: 1.5}); console.log(chalk.green(`Ticker has been updated successfully`)); break; case 'Remove Ticker': @@ -118,7 +109,7 @@ async function strActions() { console.log(chalk.yellow(`${ticker} does not exist.`)); } else { let removeTickerAction = currentContract.methods.removeTicker(tickerToRemove); - await common.sendTransaction(Issuer, removeTickerAction, defaultGasPrice, 0, 3); + await common.sendTransaction(removeTickerAction, {factor: 3}); console.log(chalk.green(`Ticker has been removed successfully`)); } break; @@ -159,15 +150,15 @@ async function strActions() { let tokenDetails = readlineSync.question(`Enter the token details: `); let deployedAt = readlineSync.questionInt(`Enter the Unix Epoch timestamp at which security token was deployed: `); let modifySTAction = currentContract.methods.modifySecurityToken(name, ticker, owner, stAddress, tokenDetails, deployedAt); - await common.sendTransaction(Issuer, modifySTAction, defaultGasPrice, 0, 1.5); + await common.sendTransaction(modifySTAction, {factor: 1.5}); console.log(chalk.green(`Security Token has been updated successfully`)); break; case 'Change Expiry Limit': let currentExpiryLimit = await currentContract.methods.getExpiryLimit().call(); console.log(chalk.yellow(`Current expiry limit is ${Math.floor(parseInt(currentExpiryLimit)/60/60/24)} days`)); - let newExpiryLimit = duration.days(readlineSync.questionInt('Enter a new value in days for expiry limit: ')); + let newExpiryLimit = gbl.constants.DURATION.days(readlineSync.questionInt('Enter a new value in days for expiry limit: ')); let changeExpiryLimitAction = currentContract.methods.changeExpiryLimit(newExpiryLimit); - let changeExpiryLimitReceipt = await common.sendTransaction(Issuer, changeExpiryLimitAction, defaultGasPrice); + let changeExpiryLimitReceipt = await common.sendTransaction(changeExpiryLimitAction); let changeExpiryLimitEvent = common.getEventFromLogs(currentContract._jsonInterface, changeExpiryLimitReceipt.logs, 'ChangeExpiryLimit'); console.log(chalk.green(`Expiry limit was changed successfully. New limit is ${Math.floor(parseInt(changeExpiryLimitEvent._newExpiry)/60/60/24)} days\n`)); break; @@ -176,7 +167,7 @@ async function strActions() { console.log(chalk.yellow(`\nCurrent ticker registration fee is ${currentRegFee} POLY`)); let newRegFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in POLY for ticker registration fee: ').toString()); let changeRegFeeAction = currentContract.methods.changeTickerRegistrationFee(newRegFee); - let changeRegFeeReceipt = await common.sendTransaction(Issuer, changeRegFeeAction, defaultGasPrice); + let changeRegFeeReceipt = await common.sendTransaction(changeRegFeeAction); let changeRegFeeEvent = common.getEventFromLogs(currentContract._jsonInterface, changeRegFeeReceipt.logs, 'ChangeTickerRegistrationFee'); console.log(chalk.green(`Fee was changed successfully. New fee is ${web3.utils.fromWei(changeRegFeeEvent._newFee)} POLY\n`)); break; @@ -185,7 +176,7 @@ async function strActions() { console.log(chalk.yellow(`\nCurrent ST launch fee is ${currentLaunchFee} POLY`)); let newLaunchFee = web3.utils.toWei(readlineSync.questionInt('Enter a new value in POLY for ST launch fee: ').toString()); let changeLaunchFeeAction = currentContract.methods.changeSecurityLaunchFee(newLaunchFee); - let changeLaunchFeeReceipt = await common.sendTransaction(Issuer, changeLaunchFeeAction, defaultGasPrice); + let changeLaunchFeeReceipt = await common.sendTransaction(changeLaunchFeeAction); let changeLaunchFeeEvent = common.getEventFromLogs(currentContract._jsonInterface, changeLaunchFeeReceipt.logs, 'ChangeSecurityLaunchFee'); console.log(chalk.green(`Fee was changed successfully. New fee is ${web3.utils.fromWei(changeLaunchFeeEvent._newFee)} POLY\n`)); break; @@ -197,7 +188,7 @@ async function strActions() { } module.exports = { - executeApp: async function(remoteNetwork) { - return executeApp(remoteNetwork); + executeApp: async function() { + return executeApp(); } } \ No newline at end of file diff --git a/CLI/commands/dividends_manager.js b/CLI/commands/dividends_manager.js index bde3aa900..20880f7c0 100644 --- a/CLI/commands/dividends_manager.js +++ b/CLI/commands/dividends_manager.js @@ -1,509 +1,711 @@ -const duration = { - seconds: function (val) { return val; }, - minutes: function (val) { return val * this.seconds(60); }, - hours: function (val) { return val * this.minutes(60); }, - days: function (val) { return val * this.hours(24); }, - weeks: function (val) { return val * this.days(7); }, - years: function (val) { return val * this.days(365); }, -}; -var readlineSync = require('readline-sync'); -var chalk = require('chalk'); -var moment = require('moment'); -var common = require('./common/common_functions'); -var global = require('./common/global'); -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis'); - -const MODULES_TYPES = { - PERMISSION: 1, - TRANSFER: 2, - STO: 3, - DIVIDENDS: 4, - BURN: 5 -} +const readlineSync = require('readline-sync'); +const chalk = require('chalk'); +const moment = require('moment'); +const common = require('./common/common_functions'); +const gbl = require('./common/global'); +const contracts = require('./helpers/contract_addresses'); +const abis = require('./helpers/contract_abis'); +const csvParse = require('./helpers/csv'); +const BigNumber = require('bignumber.js'); +const { table } = require('table') + +const EXCLUSIONS_DATA_CSV = `${__dirname}/../data/Checkpoint/exclusions_data.csv`; +const TAX_WITHHOLDING_DATA_CSV = `${__dirname}/../data/Checkpoint/tax_withholding_data.csv`; // App flow let tokenSymbol; let securityToken; let polyToken; let securityTokenRegistry; -let generalTransferManager; +let moduleRegistry; let currentDividendsModule; -async function executeApp(type, remoteNetwork) { - dividendsType = type; - await global.initialize(remoteNetwork); +let dividendsType; - common.logAsciiBull(); - console.log("**********************************************"); - console.log("Welcome to the Command-Line Dividends Manager."); - console.log("**********************************************"); - console.log("Issuer Account: " + Issuer.address + "\n"); +async function executeApp() { + console.log('\n', chalk.blue('Dividends Manager - Main Menu', '\n')); - await setup(); - try { - await start_explorer(); - } catch (err) { - console.log(err); - return; + let tmModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.DIVIDENDS); + let nonArchivedModules = tmModules.filter(m => !m.archived); + if (nonArchivedModules.length > 0) { + console.log(`Dividends modules attached:`); + nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + } else { + console.log(`There are no dividends modules attached`); } -}; -async function setup(){ - try { - let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - securityTokenRegistry.setProvider(web3.currentProvider); + let currentCheckpoint = await securityToken.methods.currentCheckpointId().call(); + if (currentCheckpoint > 0) { + console.log(`\nCurrent checkpoint: ${currentCheckpoint}`); + } - let polyTokenAddress = await contracts.polyToken(); - let polyTokenABI = abis.polyToken(); - polyToken = new web3.eth.Contract(polyTokenABI, polyTokenAddress); - polyToken.setProvider(web3.currentProvider); - } catch (err) { - console.log(err) - console.log('\x1b[31m%s\x1b[0m',"There was a problem getting the contracts. Make sure they are deployed to the selected network."); - process.exit(0); + let options = ['Create checkpoint', 'Explore address balances']; + if (nonArchivedModules.length > 0) { + options.push('Config existing modules'); + } + options.push('Add new dividends module'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); + let optionSelected = index != -1 ? options[index] : 'EXIT'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Create checkpoint': + await createCheckpointFromST(); + break; + case 'Explore address balances': + await exploreAddress(currentCheckpoint); + break; + case 'Config existing modules': + await configExistingModules(nonArchivedModules); + break; + case 'Add new dividends module': + await addDividendsModule(); + break; + case 'EXIT': + return; } + + await executeApp(); } -async function start_explorer(){ - console.log('\n\x1b[34m%s\x1b[0m',"Dividends Manager - Main Menu"); +async function createCheckpointFromST() { + let createCheckpointAction = securityToken.methods.createCheckpoint(); + let receipt = await common.sendTransaction(createCheckpointAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'CheckpointCreated'); + console.log(chalk.green(`Checkpoint ${event._checkpointId} has been created successfully!`)); +} - if (!tokenSymbol) - tokenSymbol = readlineSync.question('Enter the token symbol: '); +async function exploreAddress(currentCheckpoint) { + let address = readlineSync.question('Enter address to explore: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + let checkpoint = null; + if (currentCheckpoint > 0) { + checkpoint = await selectCheckpoint(false); + } - let result = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (result == "0x0000000000000000000000000000000000000000") { - tokenSymbol = undefined; - console.log(chalk.red(`Token symbol provided is not a registered Security Token.`)); - } else { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI,result); + let balance = web3.utils.fromWei(await securityToken.methods.balanceOf(address).call()); + let totalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + console.log(`Balance of ${address} is: ${balance} ${tokenSymbol}`); + console.log(`TotalSupply is: ${totalSupply} ${tokenSymbol}`); - // Get the GTM - result = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); - if (result.length == 0) { - console.log(chalk.red(`General Transfer Manager is not attached.`)); - } else { - generalTransferManagerAddress = result[0]; - let generalTransferManagerABI = abis.generalTransferManager(); - generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, generalTransferManagerAddress); - generalTransferManager.setProvider(web3.currentProvider); - - let typeOptions = ['POLY', 'ETH']; - if (!typeOptions.includes(dividendsType)) { - let index = readlineSync.keyInSelect(typeOptions, 'What type of dividends do you want work with?', {cancel: false}); - dividendsType = typeOptions[index]; - console.log(`Selected: ${dividendsType}`) - } - - let currentCheckpoint = await securityToken.methods.currentCheckpointId().call(); - console.log(chalk.yellow(`\nToken is at checkpoint: ${currentCheckpoint}`)); + if (checkpoint) { + let balanceAt = web3.utils.fromWei(await securityToken.methods.balanceOfAt(address, checkpoint).call()); + let totalSupplyAt = web3.utils.fromWei(await securityToken.methods.totalSupplyAt(checkpoint).call()); + console.log(`Balance of ${address} at checkpoint ${checkpoint}: ${balanceAt} ${tokenSymbol}`); + console.log(`TotalSupply at checkpoint ${checkpoint} is: ${totalSupplyAt} ${tokenSymbol}`); + } +} - let options = ['Mint tokens', 'Transfer tokens', 'Create checkpoint', 'Set default exclusions for dividends', 'Tax holding settings', 'Create dividends'] +async function configExistingModules(dividendModules) { + let options = dividendModules.map(m => `${m.name} at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module do you want to config? ', { cancel: 'RETURN' }); + console.log('Selected:', index != -1 ? options[index] : 'RETURN', '\n'); + let moduleNameSelected = index != -1 ? dividendModules[index].name : 'RETURN'; + switch (moduleNameSelected) { + case 'ERC20DividendCheckpoint': + currentDividendsModule = new web3.eth.Contract(abis.erc20DividendCheckpoint(), dividendModules[index].address); + currentDividendsModule.setProvider(web3.currentProvider); + dividendsType = 'ERC20'; + break; + case 'EtherDividendCheckpoint': + currentDividendsModule = new web3.eth.Contract(abis.etherDividendCheckpoint(), dividendModules[index].address); + currentDividendsModule.setProvider(web3.currentProvider); + dividendsType = 'ETH'; + break; + } - if (currentCheckpoint > 0) { - options.push('Explore account at checkpoint', 'Explore total supply at checkpoint') - } + await dividendsManager(); +} - // Only show dividend options if divididenModule is already attached - if (await isDividendsModuleAttached()) { - options.push('Push dividends to accounts', - `Explore ${dividendsType} balance`, 'Reclaim expired dividends') - } +async function dividendsManager() { + console.log(chalk.blue(`Dividends module at ${currentDividendsModule.options.address}`), '\n'); - let index = readlineSync.keyInSelect(options, 'What do you want to do?'); - let selected = index != -1 ? options[index] : 'Cancel'; - console.log('Selected:', selected, '\n'); - switch (selected) { - case 'Mint tokens': - let _to = readlineSync.question('Enter beneficiary of minting: '); - let _amount = readlineSync.question('Enter amount of tokens to mint: '); - await mintTokens(_to,_amount); - break; - case 'Transfer tokens': - let _to2 = readlineSync.question('Enter beneficiary of tranfer: '); - let _amount2 = readlineSync.question('Enter amount of tokens to transfer: '); - await transferTokens(_to2,_amount2); - break; - case 'Create checkpoint': - let createCheckpointAction = securityToken.methods.createCheckpoint(); - await common.sendTransaction(Issuer, createCheckpointAction, defaultGasPrice); - break; - case 'Set default exclusions for dividends': - await setDefaultExclusions(); - break; - case 'Tax holding settings': - await taxHoldingMenu(); - break; - case 'Create dividends': - let divName = readlineSync.question(`Enter a name or title to indetify this dividend: `); - let dividend = readlineSync.question(`How much ${dividendsType} would you like to distribute to token holders?: `); - await checkBalance(dividend); - let checkpointId = currentCheckpoint == 0 ? 0 : await selectCheckpoint(true); // If there are no checkpoints, it must create a new one - await createDividends(divName, dividend, checkpointId); - break; - case 'Explore account at checkpoint': - let _address = readlineSync.question('Enter address to explore: '); - let _checkpoint = await selectCheckpoint(false); - await exploreAddress(_address, _checkpoint); - break; - case 'Explore total supply at checkpoint': - let _checkpoint2 = await selectCheckpoint(false); - await exploreTotalSupply(_checkpoint2); - break; - case 'Push dividends to accounts': - let _dividend = await selectDividend({valid: true, expired: false, reclaimed: false, withRemaining: true}); - if (_dividend !== null) { - let _addresses = readlineSync.question('Enter addresses to push dividends to (ex- add1,add2,add3,...): '); - await pushDividends(_dividend, _addresses); - } - break; - case `Explore ${dividendsType} balance`: - let _address3 = readlineSync.question('Enter address to explore: '); - let _dividend3 = await selectDividend(); - if (_dividend3 !== null) { - let dividendAmounts = await currentDividendsModule.methods.calculateDividend(_dividend3.index, _address3).call(); - let dividendBalance = dividendAmounts[0]; - let dividendTax = dividendAmounts[1]; - let balance = await getBalance(_address3); - console.log(` - ${dividendsType} Balance: ${web3.utils.fromWei(balance)} ${dividendsType} - Dividends owned: ${web3.utils.fromWei(dividendBalance)} ${dividendsType} - Tax withheld: ${web3.utils.fromWei(dividendTax)} ${dividendsType} - `); - } - break; - case 'Reclaim expired dividends': - let _dividend4 = await selectDividend({expired: true, reclaimed: false}); - if (_dividend4 !== null) { - await reclaimedDividend(_dividend4); - } - break; - case 'Cancel': - process.exit(0); - break; - } - } - } - //Restart - await start_explorer(); -} + let wallet = await currentDividendsModule.methods.wallet().call(); + let currentDividends = await getDividends(); + let defaultExcluded = await currentDividendsModule.methods.getDefaultExcluded().call(); + let currentCheckpointId = await securityToken.methods.currentCheckpointId().call(); -async function mintTokens(address, amount){ - if (await securityToken.methods.mintingFrozen().call()) { - console.log(chalk.red("Minting is not possible - Minting has been permanently frozen by issuer")); - } else { - await whitelistAddress(address); + console.log(`- Wallet: ${wallet}`); + console.log(`- Current dividends: ${currentDividends.length}`); + console.log(`- Default exclusions: ${defaultExcluded.length}`); - try { - let mintAction = securityToken.methods.mint(address,web3.utils.toWei(amount)); - let receipt = await common.sendTransaction(Issuer, mintAction, defaultGasPrice); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(` - Minted ${web3.utils.fromWei(event.value)} tokens - to account ${event.to}` - ); - } catch (err) { - console.log(err); - console.log(chalk.red("There was an error processing the transfer transaction. \n The most probable cause for this error is one of the involved accounts not being in the whitelist or under a lockup period.")); - } + let options = ['Change wallet', 'Create checkpoint']; + if (currentCheckpointId > 0) { + options.push('Explore checkpoint'); } -} - -async function transferTokens(address, amount){ - await whitelistAddress(address); + if (defaultExcluded.length > 0) { + options.push('Show current default exclusions'); + } + options.push( + 'Set default exclusions', + 'Set tax withholding' + ); + if (currentDividends.length > 0) { + options.push('Manage existing dividends'); + } + options.push('Create new dividends', 'Reclaim ETH or ERC20 tokens from contract'); - try{ - let transferAction = securityToken.methods.transfer(address,web3.utils.toWei(amount)); - let receipt = await common.sendTransaction(Issuer, transferAction, defaultGasPrice, 0, 1.5); - let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); - console.log(` - Account ${event.from} - transferred ${web3.utils.fromWei(event.value)} tokens - to account ${event.to}` - ); - } catch (err) { - console.log(err); - console.log(chalk.red("There was an error processing the transfer transaction. \n The most probable cause for this error is one of the involved accounts not being in the whitelist or under a lockup period.")); + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let selected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', selected, '\n'); + switch (selected) { + case 'Change wallet': + await changeWallet(); + break; + case 'Create checkpoint': + await createCheckpointFromDividendModule(); + break; + case 'Explore checkpoint': + await exploreCheckpoint(); + case 'Show current default exclusions': + showExcluded(defaultExcluded); + break; + case 'Set default exclusions': + await setDefaultExclusions(); + break; + case 'Set tax withholding': + await taxWithholding(); + break; + case 'Manage existing dividends': + let selectedDividend = await selectDividend(currentDividends); + if (selectedDividend) { + await manageExistingDividend(selectedDividend.index); + } + break; + case 'Create new dividends': + await createDividends(); + break; + case 'Reclaim ETH or ERC20 tokens from contract': + await reclaimFromContract(); + break; + case 'RETURN': + return; } -} -async function exploreAddress(address, checkpoint){ - let balance = await securityToken.methods.balanceOf(address).call(); - balance = web3.utils.fromWei(balance); - console.log(`Balance of ${address} is: ${balance} (Using balanceOf)`); + await dividendsManager(); +} - let balanceAt = await securityToken.methods.balanceOfAt(address,checkpoint).call(); - balanceAt = web3.utils.fromWei(balanceAt); - console.log(`Balance of ${address} is: ${balanceAt} (Using balanceOfAt - checkpoint ${checkpoint})`); +async function changeWallet() { + let newWallet = readlineSync.question('Enter the new account address to receive reclaimed dividends and tax: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + let action = currentDividendsModule.methods.changeWallet(newWallet); + let receipt = await common.sendTransaction(action); + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetWallet'); + console.log(chalk.green(`The wallet has been changed successfully!`)); } -async function exploreTotalSupply(checkpoint){ - let totalSupply = await securityToken.methods.totalSupply().call(); - totalSupply = web3.utils.fromWei(totalSupply); - console.log(`TotalSupply is: ${totalSupply} (Using totalSupply)`); +async function createCheckpointFromDividendModule() { + let createCheckpointAction = securityToken.methods.createCheckpoint(); + await common.sendTransaction(createCheckpointAction); + console.log(chalk.green(`Checkpoint have been created successfully!`)); +} - let totalSupplyAt = await securityToken.methods.totalSupplyAt(checkpoint).call(); - totalSupplyAt = web3.utils.fromWei(totalSupplyAt); - console.log(`TotalSupply is: ${totalSupplyAt} (Using totalSupplyAt - checkpoint ${checkpoint})`); +async function exploreCheckpoint() { + let checkpoint = await selectCheckpoint(false); + + let checkpointData = await currentDividendsModule.methods.getCheckpointData(checkpoint).call(); + let dataTable = [['Investor', `Balance at checkpoint (${tokenSymbol})`, 'Tax withholding set (%)']]; + for (let i = 0; i < checkpointData.investors.length; i++) { + dataTable.push([ + checkpointData.investors[i], + web3.utils.fromWei(checkpointData.balances[i]), + parseFloat(web3.utils.fromWei(checkpointData.withholdings[i])) * 100 + ]); + } + console.log(); + console.log(table(dataTable)); } async function setDefaultExclusions() { - await addDividendsModule(); - - let excluded = await currentDividendsModule.methods.getDefaultExcluded().call(); - showExcluded(excluded); - - console.log(chalk.yellow(`Excluded addresses will be loaded from 'dividendsExclusions_data.csv'. Please check your data before continue.`)); + console.log(chalk.yellow(`Excluded addresses will be loaded from 'exclusions_data.csv'. Please check your data before continue.`)); if (readlineSync.keyInYNStrict(`Do you want to continue?`)) { let excluded = getExcludedFromDataFile(); - let setDefaultExclusionsActions = currentDividendsModule.methods.setDefaultExcluded(excluded); - let receipt = await common.sendTransaction(Issuer, setDefaultExclusionsActions, defaultGasPrice); + let setDefaultExclusionsActions = currentDividendsModule.methods.setDefaultExcluded(excluded[0]); + let receipt = await common.sendTransaction(setDefaultExclusionsActions); let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetDefaultExcludedAddresses'); - console.log(chalk.green(`Exclusions were successfully set.`)); - showExcluded(event._excluded); + console.log(chalk.green(`Exclusions have been set successfully!`)); + showExcluded(event._excluded); } } -async function taxHoldingMenu() { - await addDividendsModule(); +async function manageExistingDividend(dividendIndex) { + // Show current data + + let dividend = await currentDividendsModule.methods.dividends(dividendIndex).call(); + let dividendTokenAddress = gbl.constants.ADDRESS_ZERO; + let dividendTokenSymbol = 'ETH'; + let dividendTokenDecimals = 18; + if (dividendsType === 'ERC20') { + dividendTokenAddress = await currentDividendsModule.methods.dividendTokens(dividendIndex).call(); + dividendTokenSymbol = await getERC20TokenSymbol(dividendTokenAddress); + let erc20token = new web3.eth.Contract(abis.erc20(), dividendTokenAddress); + dividendTokenDecimals = await erc20token.methods.decimals().call(); + } + let progress = await currentDividendsModule.methods.getDividendProgress(dividendIndex).call(); + let investorArray = progress[0]; + let claimedArray = progress[1]; + let excludedArray = progress[2]; + let withheldArray = progress[3]; + let amountArray = progress[4]; + let balanceArray = progress[5]; + + // function for adding two numbers. Easy! + const add = (a, b) => { + const bnA = new web3.utils.BN(a); + const bnB = new web3.utils.BN(b); + return bnA.add(bnB).toString(); + }; + // use reduce to sum our array + let taxesToWithHeld = withheldArray.reduce(add, 0); + let claimedInvestors = claimedArray.filter(c => c).length; + let excludedInvestors = excludedArray.filter(e => e).length; + + console.log(`- Name: ${web3.utils.hexToUtf8(dividend.name)}`); + console.log(`- Created: ${moment.unix(dividend.created).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- Maturity: ${moment.unix(dividend.maturity).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- Expiry: ${moment.unix(dividend.expiry).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- At checkpoint: ${dividend.checkpointId}`); + console.log(`- Amount: ${dividend.amount / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol}`); + console.log(`- Claimed amount: ${dividend.claimedAmount / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol}`); + console.log(`- Taxes:`); + console.log(` To withhold: ${taxesToWithHeld / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol}`); + console.log(` Withheld to-date: ${dividend.totalWithheld / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol}`); + console.log(` Withdrawn to-date: ${dividend.totalWithheldWithdrawn / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol}`); + console.log(`- Total investors: ${investorArray.length}`); + console.log(` Have already claimed: ${claimedInvestors} (${investorArray.length - excludedInvestors !== 0 ? claimedInvestors / (investorArray.length - excludedInvestors) * 100 : 0}%)`); + console.log(` Excluded: ${excludedInvestors} `); + // ------------------ + + + let options = ['Show investors', 'Show report', 'Explore account']; + if (isValidDividend(dividend) && hasRemaining(dividend) && !isExpiredDividend(dividend) && !dividend.reclaimed) { + options.push('Push dividends to accounts'); + } + if (hasRemainingWithheld(dividend)) { + options.push('Withdraw withholding'); + } + if (isExpiredDividend(dividend) && !dividend.reclaimed) { + options.push('Reclaim expired dividends'); + } - let options = ['Set a % to withhold from dividends sent to an address', 'Withdraw withholding for dividend', 'Return to main menu']; - let index = readlineSync.keyInSelect(options, 'What do you want to do?', {cancel: false}); - let selected = options[index]; - console.log("Selected:", selected); - switch (selected) { - case 'Set a % to withhold from dividends sent to an address': - let address = readlineSync.question('Enter the address of the investor: ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - let percentage = readlineSync.question('Enter the percentage of dividends to withhold (number between 0-100): ', { - limit: function(input) { - return (parseInt(input) >= 0 && parseInt(input) <= 100); - }, - limitMessage: "Must be a value between 0 and 100", - }); - let percentageWei = web3.utils.toWei((percentage / 100).toString()); - let setWithHoldingFixedAction = currentDividendsModule.methods.setWithholdingFixed([address], percentageWei); - let receipt = await common.sendTransaction(Issuer, setWithHoldingFixedAction, defaultGasPrice); - console.log(chalk.green(`Successfully set tax withholding of ${percentage}% for ${address}.`)); + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Show investors': + showInvestors(investorArray, claimedArray, excludedArray); break; - case 'Withdraw withholding for dividend': - let _dividend = await selectDividend({withRemainingWithheld: true}); - if (_dividend !== null) { - let withdrawWithholdingAction = currentDividendsModule.methods.withdrawWithholding(_dividend.index); - let receipt = await common.sendTransaction(Issuer, withdrawWithholdingAction, defaultGasPrice); - let eventName; - if (dividendsType == 'POLY') { - eventName = 'ERC20DividendWithholdingWithdrawn'; - } else if (dividendsType == 'ETH') { - eventName = 'EtherDividendWithholdingWithdrawn'; - } - let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, eventName); - console.log(chalk.green(`Successfully withdrew ${web3.utils.fromWei(event._withheldAmount)} ${dividendsType} from dividend ${_dividend.index} tax withholding.`)); - } + case 'Show report': + showReport( + web3.utils.hexToUtf8(dividend.name), + dividendTokenSymbol, + dividendTokenDecimals, + dividend.amount, // Total amount of dividends sent + dividend.totalWithheld, // Total amount of taxes withheld + dividend.claimedAmount, // Total amount of dividends distributed + investorArray, // Per Address(Amount sent, Taxes withheld (%), Taxes withheld ($/ETH/# Tokens), Amount received, Withdrawn (TRUE/FALSE) + claimedArray, + excludedArray, + withheldArray, + amountArray + ); break; - case 'Return to main menu': + case 'Push dividends to accounts': + await pushDividends(dividendIndex, dividend.checkpointId); break; + case 'Explore account': + await exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol, dividendTokenDecimals); + break; + case 'Withdraw withholding': + await withdrawWithholding(dividendIndex, dividendTokenSymbol, dividendTokenDecimals); + break; + case 'Reclaim expired dividends': + await reclaimedDividend(dividendIndex, dividendTokenSymbol, dividendTokenDecimals); + return; + case 'Reclaim ETH or ERC20 tokens from contract': + await reclaim + break; + case 'RETURN': + return; } -} -async function createDividends(name, dividend, checkpointId) { - await addDividendsModule(); + await manageExistingDividend(dividendIndex); +} - let time = Math.floor(Date.now()/1000); - let maturityTime = readlineSync.questionInt('Enter the dividend maturity time from which dividend can be paid (Unix Epoch time)\n(Now = ' + time + ' ): ', {defaultInput: time}); - let defaultTime = time + duration.minutes(10); - let expiryTime = readlineSync.questionInt('Enter the dividend expiry time (Unix Epoch time)\n(10 minutes from now = ' + defaultTime + ' ): ', {defaultInput: defaultTime}); - - let useDefaultExcluded = readlineSync.keyInYNStrict(`Do you want to use the default excluded addresses for this dividend? If not, data from 'dividendsExclusions_data.csv' will be used instead.`); +async function taxWithholding() { + let addresses = readlineSync.question(`Enter addresses to set tax withholding to(ex - add1, add2, add3, ...) or leave empty to read from 'tax_withholding_data.csv': `, { + limit: function (input) { + return input === '' || (input.split(',').every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(','); + if (addresses[0] !== '') { + let percentage = readlineSync.question('Enter the percentage of dividends to withhold (number between 0-100): ', { + limit: function (input) { + return (parseFloat(input) >= 0 && parseFloat(input) <= 100); + }, + limitMessage: 'Must be a value between 0 and 100', + }); + let percentageWei = web3.utils.toWei((percentage / 100).toString()); + let setWithHoldingFixedAction = currentDividendsModule.methods.setWithholdingFixed(addresses, percentageWei); + let receipt = await common.sendTransaction(setWithHoldingFixedAction); + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'SetWithholdingFixed'); + console.log(chalk.green(`Successfully set tax rate of ${web3.utils.fromWei(event._withholding)}% for: `)); + console.log(chalk.green(event._investors)); + } else { + let parsedData = csvParse(TAX_WITHHOLDING_DATA_CSV); + let validData = parsedData.filter(row => + web3.utils.isAddress(row[0]) && + !isNaN(row[1]) + ); + 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, 100); + let [investorArray, taxArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + taxArray[batch] = taxArray[batch].map(t => web3.utils.toWei((t / 100).toString())); + console.log(`Batch ${batch + 1} - Attempting to set multiple tax rates to accounts: \n\n`, investorArray[batch], '\n'); + let action = await currentDividendsModule.methods.setWithholding(investorArray[batch], taxArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Multiple tax rates have benn set successfully!')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } + } +} - let createDividendAction; - if (dividendsType == 'POLY') { - let approveAction = polyToken.methods.approve(currentDividendsModule._address, web3.utils.toWei(dividend)); - await common.sendTransaction(Issuer, approveAction, defaultGasPrice); - if (checkpointId > 0) { - if (useDefaultExcluded) { - createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), checkpointId, web3.utils.toHex(name)); - } else { - let excluded = getExcludedFromDataFile(); - createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), checkpointId, excluded, web3.utils.toHex(name)); - } - } else { - if (useDefaultExcluded) { - createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), web3.utils.toHex(name)); +async function createDividends() { + let dividendName = readlineSync.question(`Enter a name or title to indetify this dividend: `); + let dividendToken = gbl.constants.ADDRESS_ZERO; + let dividendSymbol = 'ETH'; + let dividendTokenDecimals = 18; + let token; + if (dividendsType === 'ERC20') { + do { + dividendToken = readlineSync.question(`Enter the address of ERC20 token in which dividend will be denominated(POLY = ${polyToken.options.address}): `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid ERC20 address", + defaultInput: polyToken.options.address + }); + let erc20Symbol = await getERC20TokenSymbol(dividendToken); + if (erc20Symbol != null) { + token = new web3.eth.Contract(abis.erc20(), dividendToken); + dividendSymbol = erc20Symbol; + dividendTokenDecimals = await token.methods.decimals().call(); } else { - let excluded = getExcludedFromDataFile(); - createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, polyToken._address, web3.utils.toWei(dividend), excluded, web3.utils.toHex(name)); + console.log(chalk.red(`${dividendToken} is not a valid ERC20 token address!!`)); } - } - let receipt = await common.sendTransaction(Issuer, createDividendAction, defaultGasPrice); - let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'ERC20DividendDeposited'); - console.log(chalk.green(`Dividend ${event._dividendIndex} deposited`)); - } else if (dividendsType == 'ETH') { - if (checkpointId > 0) { - if (useDefaultExcluded) { - createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, checkpointId, web3.utils.toHex(name)); + } while (dividendSymbol === 'ETH'); + } + let dividendAmount = readlineSync.question(`How much ${dividendSymbol} would you like to distribute to token holders? `); + + let dividendAmountBN = new BigNumber(dividendAmount).times(Math.pow(10, dividendTokenDecimals)); + let issuerBalance = new BigNumber(await getBalance(Issuer.address, dividendToken)); + if (issuerBalance.lt(dividendAmountBN)) { + console.log(chalk.red(`You have ${issuerBalance / Math.pow(10, dividendTokenDecimals)} ${dividendSymbol}. You need ${(dividendAmountBN - issuerBalance) / Math.pow(10, dividendTokenDecimals)} ${dividendSymbol} more!`)); + } else { + let checkpointId = await selectCheckpoint(true); // If there are no checkpoints, it must create a new one + let now = Math.floor(Date.now() / 1000); + let maturityTime = readlineSync.questionInt('Enter the dividend maturity time from which dividend can be paid (Unix Epoch time)\n(Now = ' + now + ' ): ', { defaultInput: now }); + let defaultTime = now + gbl.constants.DURATION.minutes(10); + let expiryTime = readlineSync.questionInt('Enter the dividend expiry time (Unix Epoch time)\n(10 minutes from now = ' + defaultTime + ' ): ', { defaultInput: defaultTime }); + + let useDefaultExcluded = !readlineSync.keyInYNStrict(`Do you want to use data from 'dividends_exclusions_data.csv' for this dividend? If not, default exclusions will apply.`); + + let createDividendAction; + if (dividendsType == 'ERC20') { + let approveAction = token.methods.approve(currentDividendsModule._address, dividendAmountBN); + await common.sendTransaction(approveAction); + if (checkpointId > 0) { + if (useDefaultExcluded) { + createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, token.options.address, dividendAmountBN, checkpointId, web3.utils.toHex(dividendName)); + } else { + let excluded = getExcludedFromDataFile(); + createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, token.options.address, dividendAmountBN, checkpointId, excluded[0], web3.utils.toHex(dividendName)); + } } else { - let excluded = getExcludedFromDataFile(); - createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, checkpointId, excluded, web3.utils.toHex(name)); + if (useDefaultExcluded) { + createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, token.options.address, dividendAmountBN, web3.utils.toHex(dividendName)); + } else { + let excluded = getExcludedFromDataFile(); + createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, token.options.address, dividendAmountBN, excluded[0], web3.utils.toHex(dividendName)); + } } + let receipt = await common.sendTransaction(createDividendAction); + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'ERC20DividendDeposited'); + console.log(chalk.green(`Dividend ${event._dividendIndex} deposited`)); } else { - if (useDefaultExcluded) { - createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, web3.utils.toHex(name)); + if (checkpointId > 0) { + if (useDefaultExcluded) { + createDividendAction = currentDividendsModule.methods.createDividendWithCheckpoint(maturityTime, expiryTime, checkpointId, web3.utils.toHex(dividendName)); + } else { + let excluded = getExcludedFromDataFile(); + createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, checkpointId, excluded, web3.utils.toHex(dividendName)); + } } else { - let excluded = getExcludedFromDataFile(); - createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, excluded, web3.utils.toHex(name)); + if (useDefaultExcluded) { + createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, web3.utils.toHex(dividendName)); + } else { + let excluded = getExcludedFromDataFile(); + createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, excluded, web3.utils.toHex(dividendName)); + } } - } - let receipt = await common.sendTransaction(Issuer, createDividendAction, defaultGasPrice, web3.utils.toWei(dividend)); - let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'EtherDividendDeposited'); - console.log(` - Dividend ${event._dividendIndex} deposited` - ); - } -} - -async function pushDividends(dividend, account){ - let accs = account.split(','); - let pushDividendPaymentToAddressesAction = currentDividendsModule.methods.pushDividendPaymentToAddresses(dividend.index, accs); - let receipt = await common.sendTransaction(Issuer, pushDividendPaymentToAddressesAction, defaultGasPrice); - let successEventName; - if (dividendsType == 'POLY') { - successEventName = 'ERC20DividendClaimed'; - } else if (dividendsType == 'ETH') { - successEventName = 'EtherDividendClaimed'; - let failedEventName = 'EtherDividendClaimFailed'; - let failedEvents = common.getMultipleEventsFromLogs(currentDividendsModule._jsonInterface, receipt.logs, failedEventName); - for (const event of failedEvents) { + let receipt = await common.sendTransaction(createDividendAction, { value: dividendAmountBN }); + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'EtherDividendDeposited'); console.log(` - Failed to claim ${web3.utils.fromWei(event._amount)} ${dividendsType} - to account ${event._payee}` +Dividend ${ event._dividendIndex} deposited` ); } } +} - let successEvents = common.getMultipleEventsFromLogs(currentDividendsModule._jsonInterface, receipt.logs, successEventName); - for (const event of successEvents) { - console.log(` - Claimed ${web3.utils.fromWei(event._amount)} ${dividendsType} - to account ${event._payee} - ${web3.utils.fromWei(event._withheld)} ${dividendsType} of tax withheld` - ); +async function reclaimFromContract() { + let options = ['ETH', 'ERC20']; + let index = readlineSync.keyInSelect(options, 'What do you want to reclaim?', { cancel: 'RETURN' }); + let selected = index != -1 ? options[index] : 'RETURN'; + switch (selected) { + case 'ETH': + let ethBalance = await web3.eth.getBalance(currentDividendsModule.options.address); + console.log(chalk.yellow(`Current ETH balance: ${web3.utils.fromWei(ethBalance)} ETH`)); + let reclaimETHAction = currentDividendsModule.methods.reclaimETH(); + await common.sendTransaction(reclaimETHAction); + console.log(chalk.green('ETH has been reclaimed succesfully!')); + break; + case 'ERC20': + let erc20Address = readlineSync.question('Enter the ERC20 token address to reclaim (POLY = ' + polyToken.options.address + '): ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: polyToken.options.address + }); + let reclaimERC20Action = currentDividendsModule.methods.reclaimERC20(erc20Address); + await common.sendTransaction(reclaimERC20Action); + console.log(chalk.green('ERC20 has been reclaimed succesfully!')); + break } } -async function reclaimedDividend(dividend) { - let reclaimDividendAction = currentDividendsModule.methods.reclaimDividend(dividend.index); - let receipt = await common.sendTransaction(Issuer, reclaimDividendAction, defaultGasPrice); - let eventName; - if (dividendsType == 'POLY') { - eventName = 'ERC20DividendReclaimed'; - } else if (dividendsType == 'ETH') { - eventName = 'EtherDividendReclaimed'; +function showInvestors(investorsArray, claimedArray, excludedArray) { + let dataTable = [['Investor', 'Has claimed', 'Is excluded']]; + for (let i = 0; i < investorsArray.length; i++) { + dataTable.push([ + investorsArray[i], + claimedArray[i] ? 'YES' : 'NO', + excludedArray[i] ? 'YES' : 'NO' + ]); } - let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, eventName); - console.log(` - Reclaimed Amount ${web3.utils.fromWei(event._claimedAmount)} ${dividendsType} - to account ${event._claimer}` - ); + console.log(); + console.log(table(dataTable)); } -async function whitelistAddress(address) { - let now = Math.floor(Date.now() / 1000); - let modifyWhitelistAction = generalTransferManager.methods.modifyWhitelist(address, now, now, now + 31536000, true); - await common.sendTransaction(Issuer, modifyWhitelistAction, defaultGasPrice); - console.log(chalk.green(`\nWhitelisting successful for ${address}.`)); +function showReport(_name, _tokenSymbol, _tokenDecimals, _amount, _witthheld, _claimed, _investorArray, _claimedArray, _excludedArray, _withheldArray, _amountArray) { + let title = `${_name.toUpperCase()} DIVIDEND REPORT`; + let dataTable = + [[ + 'Investor', + 'Amount sent', + 'Taxes withheld (%)', + `Taxes withheld (${_tokenSymbol})`, + 'Amount received', + 'Withdrawn' + ]]; + for (let i = 0; i < _investorArray.length; i++) { + let investor = _investorArray[i]; + let excluded = _excludedArray[i]; + let withdrawn = _claimedArray[i] ? 'YES' : 'NO'; + let amount = !excluded ? new BigNumber(_amountArray[i]).plus(_withheldArray[i]).div((Math.pow(10, _tokenDecimals))) : 0; + let withheld = !excluded ? parseFloat(_withheldArray[i]) / Math.pow(10, _tokenDecimals) : 'NA'; + let withheldPercentage = (!excluded) ? (withheld !== '0' ? parseFloat(withheld) / parseFloat(amount) * 100 : 0) : 'NA'; + let received = !excluded ? parseFloat(_amountArray[i]) / Math.pow(10, _tokenDecimals) : 0; + dataTable.push([ + investor, + amount, + withheldPercentage, + withheld, + received, + withdrawn + ]); + } + console.log(chalk.yellow(`-----------------------------------------------------------------------------------------------------------------------------------------------------------`)); + console.log(title.padStart((50 - title.length) / 2, '*').padEnd((50 - title.length) / 2, '*')); + console.log(); + console.log(`- Total amount of dividends sent: ${parseFloat(_amount) / Math.pow(10, _tokenDecimals)} ${_tokenSymbol} `); + console.log(`- Total amount of taxes withheld: ${parseFloat(_witthheld) / Math.pow(10, _tokenDecimals)} ${_tokenSymbol} `); + console.log(`- Total amount of dividends distributed: ${parseFloat(_claimed) / Math.pow(10, _tokenDecimals)} ${_tokenSymbol} `); + console.log(`- Total amount of investors: ${_investorArray.length} `); + console.log(); + console.log(table(dataTable)); + console.log(); + console.log(chalk.yellow(`NOTE: If investor has not claimed the dividend yet, TAX and AMOUNT are calculated with the current values set and they might change.`)); + console.log(chalk.yellow(`-----------------------------------------------------------------------------------------------------------------------------------------------------------`)); + console.log(); } -// Helper functions -async function getBalance(address) { - let balance; - if (dividendsType == 'POLY') { - balance = (await polyToken.methods.balanceOf(address).call()).toString(); - } else if (dividendsType == 'ETH') { - balance = (await web3.eth.getBalance(address)).toString(); - } - - return balance; -} -async function checkBalance(_dividend) { - let issuerBalance = await getBalance(Issuer.address); - if (parseInt(web3.utils.fromWei(issuerBalance)) < parseInt(_dividend)) { - console.log(chalk.red(` - You have ${web3.utils.fromWei(issuerBalance)} ${dividendsType} need ${(parseInt(_dividend) - parseInt(web3.utils.fromWei(issuerBalance)))} more ${dividendsType} - `)); - process.exit(0); +async function pushDividends(dividendIndex, checkpointId) { + let accounts = readlineSync.question('Enter addresses to push dividends to (ex- add1,add2,add3,...) or leave empty to push to all addresses: ', { + limit: function (input) { + return input === '' || (input.split(',').every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(','); + if (accounts[0] !== '') { + let action = currentDividendsModule.methods.pushDividendPaymentToAddresses(dividendIndex, accounts); + let receipt = await common.sendTransaction(action); + logPushResults(receipt); + } else { + let investorsAtCheckpoint = await securityToken.methods.getInvestorsAt(checkpointId).call(); + console.log(`There are ${investorsAtCheckpoint.length} investors at checkpoint ${checkpointId} `); + let batchSize = readlineSync.questionInt(`How many investors per transaction do you want to push to? `); + for (let i = 0; i < investorsAtCheckpoint.length; i += batchSize) { + let action = currentDividendsModule.methods.pushDividendPayment(dividendIndex, i, batchSize); + let receipt = await common.sendTransaction(action); + logPushResults(receipt); + } } } -async function isDividendsModuleAttached() { - let dividendsModuleName; - if (dividendsType == 'POLY') { - dividendsModuleName = 'ERC20DividendCheckpoint'; - } else if (dividendsType == 'ETH') { - dividendsModuleName = 'EtherDividendCheckpoint'; - } +async function exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol, dividendTokenDecimals) { + let account = readlineSync.question('Enter address to explore: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + let isExcluded = await currentDividendsModule.methods.isExcluded(account, dividendIndex).call(); + let hasClaimed = await currentDividendsModule.methods.isClaimed(account, dividendIndex).call(); + let dividendAmounts = await currentDividendsModule.methods.calculateDividend(dividendIndex, account).call(); + let dividendBalance = dividendAmounts[0]; + let dividendTax = dividendAmounts[1]; + let dividendTokenBalance = await getBalance(account, dividendTokenAddress); + let securityTokenBalance = await getBalance(account, securityToken.options.address); - let result = await securityToken.methods.getModulesByName(web3.utils.toHex(dividendsModuleName)).call(); - if (result.length > 0) { - let dividendsModuleAddress = result[0]; - let dividendsModuleABI; - if (dividendsType == 'POLY') { - dividendsModuleABI = abis.erc20DividendCheckpoint(); - } else if (dividendsType == 'ETH') { - dividendsModuleABI = abis.etherDividendCheckpoint(); + console.log(); + console.log(`Security token balance: ${web3.utils.fromWei(securityTokenBalance)} ${tokenSymbol} `); + console.log(`Dividend token balance: ${dividendTokenBalance / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} `); + console.log(`Is excluded: ${isExcluded ? 'YES' : 'NO'} `); + if (!isExcluded) { + console.log(`Has claimed: ${hasClaimed ? 'YES' : 'NO'} `); + if (!hasClaimed) { + console.log(`Dividends available: ${dividendBalance / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} `); + console.log(`Tax withheld: ${dividendTax / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} `); } - currentDividendsModule = new web3.eth.Contract(dividendsModuleABI, dividendsModuleAddress); - currentDividendsModule.setProvider(web3.currentProvider); } + console.log(); +} - return (typeof currentDividendsModule !== 'undefined'); +async function withdrawWithholding(dividendIndex, dividendTokenSymbol, dividendTokenDecimals) { + let action = currentDividendsModule.methods.withdrawWithholding(dividendIndex); + let receipt = await common.sendTransaction(action); + let eventName = dividendsType === 'ERC20' ? 'ERC20DividendWithholdingWithdrawn' : 'EtherDividendWithholdingWithdrawn'; + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, eventName); + console.log(chalk.green(`Successfully withdrew ${event._withheldAmount / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} from dividend ${event._dividendIndex} tax withholding.`)); +} + +async function reclaimedDividend(dividendIndex, dividendTokenSymbol, dividendTokenDecimals) { + let action = currentDividendsModule.methods.reclaimDividend(dividendIndex); + let receipt = await common.sendTransaction(action); + let eventName = dividendsType === 'ERC20' ? 'ERC20DividendReclaimed' : 'EtherDividendReclaimed'; + let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, eventName); + console.log(` +Reclaimed amount ${event._claimedAmount / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} +to account ${ event._claimer} ` + ); } async function addDividendsModule() { - if (!(await isDividendsModuleAttached())) { - let dividendsFactoryName; - let dividendsModuleABI; - if (dividendsType == 'POLY') { - dividendsFactoryName = 'ERC20DividendCheckpoint'; - dividendsModuleABI = abis.erc20DividendCheckpoint(); - } else if (dividendsType == 'ETH') { - dividendsFactoryName = 'EtherDividendCheckpoint'; - dividendsModuleABI = abis.etherDividendCheckpoint(); - } + let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(gbl.constants.MODULES_TYPES.DIVIDENDS, securityToken.options.address).call(); + let moduleList = await Promise.all(availableModules.map(async function (m) { + let moduleFactoryABI = abis.moduleFactory(); + let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); + let moduleName = web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); + let moduleVersion = await moduleFactory.methods.version().call(); + return { name: moduleName, version: moduleVersion, factoryAddress: m }; + })); + + let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); + + let index = readlineSync.keyInSelect(options, 'Which dividends module do you want to add? ', { cancel: 'Return' }); + if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]}? `)) { + let wallet = readlineSync.question('Enter the account address to receive reclaimed dividends and tax: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + let configureFunction = abis.erc20DividendCheckpoint().find(o => o.name === 'configure' && o.type === 'function'); + let bytes = web3.eth.abi.encodeFunctionCall(configureFunction, [wallet]); - let dividendsFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, MODULES_TYPES.DIVIDENDS, dividendsFactoryName); - let addModuleAction = securityToken.methods.addModule(dividendsFactoryAddress, web3.utils.fromAscii('', 16), 0, 0); - let receipt = await common.sendTransaction(Issuer, addModuleAction, defaultGasPrice); + let selectedDividendFactoryAddress = moduleList[index].factoryAddress; + let addModuleAction = securityToken.methods.addModule(selectedDividendFactoryAddress, bytes, 0, 0); + let receipt = await common.sendTransaction(addModuleAction); let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); - console.log(`Module deployed at address: ${event._module}`); - currentDividendsModule = new web3.eth.Contract(dividendsModuleABI, event._module); - currentDividendsModule.setProvider(web3.currentProvider); + console.log(chalk.green(`Module deployed at address: ${event._module} `)); } } -async function selectCheckpoint(includeCreate) { - let options = []; - let fix = 1; //Checkpoint 0 is not included, so I need to add 1 to fit indexes for checkpoints and options - let checkpoints = (await getCheckpoints()).map(function(c) { return c.timestamp }); - if (includeCreate) { - options.push('Create new checkpoint'); - fix = 0; //If this option is added, fix isn't needed. +// Helper functions +async function getBalance(address, tokenAddress) { + if (tokenAddress !== gbl.constants.ADDRESS_ZERO) { + let token = new web3.eth.Contract(abis.erc20(), tokenAddress); + return await token.methods.balanceOf(address).call(); + } else { + return await web3.eth.getBalance(address); + } +} + +function logPushResults(receipt) { + let successEventName; + if (dividendsType == 'ERC20') { + successEventName = 'ERC20DividendClaimed'; } - options = options.concat(checkpoints); + else if (dividendsType == 'ETH') { + successEventName = 'EtherDividendClaimed'; + let failedEventName = 'EtherDividendClaimFailed'; + let failedEvents = common.getMultipleEventsFromLogs(currentDividendsModule._jsonInterface, receipt.logs, failedEventName); + for (const event of failedEvents) { + console.log(chalk.red(`Failed to claim ${web3.utils.fromWei(event._amount)} ${dividendsType} to account ${event._payee} `, '\n')); + } + } + let successEvents = common.getMultipleEventsFromLogs(currentDividendsModule._jsonInterface, receipt.logs, successEventName); + for (const event of successEvents) { + console.log(chalk.green(` Claimed ${web3.utils.fromWei(event._amount)} ${dividendsType} +to account ${ event._payee} +${ web3.utils.fromWei(event._withheld)} ${dividendsType} of tax withheld`, '\n')); + } +} - return readlineSync.keyInSelect(options, 'Select a checkpoint:', {cancel: false}) + fix; +async function selectCheckpoint(includeCreate) { + if (await securityToken.methods.currentCheckpointId().call() > 0) { + let options = []; + let fix = 1; //Checkpoint 0 is not included, so I need to add 1 to fit indexes for checkpoints and options + let checkpoints = (await getCheckpoints()).map(function (c) { return c.timestamp }); + if (includeCreate) { + options.push('Create new checkpoint'); + fix = 0; //If this option is added, fix isn't needed. + } + options = options.concat(checkpoints); + + return readlineSync.keyInSelect(options, 'Select a checkpoint:', { cancel: false }) + fix; + } else { + return 0; + } } async function getCheckpoints() { let result = []; - + let checkPointsTimestamps = await securityToken.methods.getCheckpointTimes().call(); for (let index = 0; index < checkPointsTimestamps.length; index++) { let checkpoint = {}; @@ -515,91 +717,237 @@ async function getCheckpoints() { return result.sort((a, b) => a.id - b.id); } -async function selectDividend(filter) { - let result = null; - let dividends = await getDividends(); +function isValidDividend(dividend) { + let now = Math.floor(Date.now() / 1000); + return now > dividend.maturity; +} - let now = Math.floor(Date.now()/1000); - if (typeof filter !== 'undefined') { - if (typeof filter.valid !== 'undefined') { - dividends = dividends.filter(d => filter.valid == (now > d.maturity)); - } - if (typeof filter.expired !== 'undefined') { - dividends = dividends.filter(d => filter.expired == (d.expiry < now)); - } - if (typeof filter.reclaimed !== 'undefined') { - dividends = dividends.filter(d => filter.reclaimed == d.reclaimed); - } - if (typeof filter.withRemainingWithheld !== 'undefined') { - dividends = dividends.filter(d => new web3.utils.BN(d.dividendWithheld).sub(new web3.utils.BN(d.dividendWithheldReclaimed)) > 0); - } - if (typeof filter.withRemaining !== 'undefined') { - dividends = dividends.filter(d => new web3.utils.BN(d.amount).sub(new web3.utils.BN(d.claimedAmount)) > 0); - } - } +function isExpiredDividend(dividend) { + let now = Math.floor(Date.now() / 1000); + return now > dividend.expiry; +} - if (dividends.length > 0) { - let options = dividends.map(function(d) { - return `${web3.utils.toAscii(d.name)} +function hasRemaining(dividend) { + return Number(new web3.utils.BN(dividend.amount).sub(new web3.utils.BN(dividend.claimedAmount))).toFixed(10) > 0; +} + +function hasRemainingWithheld(dividend) { + return Number(new web3.utils.BN(dividend.dividendWithheld).sub(new web3.utils.BN(dividend.dividendWithheldReclaimed))).toFixed(10) > 0; +} + +async function selectDividend(dividends) { + let result = null; + let options = dividends.map(function (d) { + return `${d.name} + Amount: ${parseFloat(d.amount) / Math.pow(10, d.tokenDecimals)} ${d.tokenSymbol} + Status: ${isExpiredDividend(d) ? 'Expired' : hasRemaining(d) ? 'In progress' : 'Completed'} + Token: ${d.tokenSymbol} Created: ${moment.unix(d.created).format('MMMM Do YYYY, HH:mm:ss')} - Maturity: ${moment.unix(d.maturity).format('MMMM Do YYYY, HH:mm:ss')} - Expiry: ${moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')} - At checkpoint: ${d.checkpointId} - Amount: ${web3.utils.fromWei(d.amount)} ${dividendsType} - Claimed Amount: ${web3.utils.fromWei(d.claimedAmount)} ${dividendsType} - Withheld: ${web3.utils.fromWei(d.dividendWithheld)} ${dividendsType} - Withheld claimed: ${web3.utils.fromWei(d.dividendWithheldReclaimed)} ${dividendsType}` - }); + Expiry: ${moment.unix(d.expiry).format('MMMM Do YYYY, HH:mm:ss')} ` + }); - let index = readlineSync.keyInSelect(options, 'Select a dividend:'); - if (index != -1) { - result = dividends[index]; - } - } else { - console.log(chalk.red(`No dividends were found meeting the requirements`)) - console.log(chalk.red(`Requirements: Valid: ${filter.valid} - Expired: ${filter.expired} - Reclaimed: ${filter.reclaimed} - WithRemainingWithheld: ${filter.withRemainingWithheld} - WithRemaining: ${filter.withRemaining}\n`)) + let index = readlineSync.keyInSelect(options, 'Select a dividend:', { cancel: 'RETURN' }); + if (index != -1) { + result = dividends[index]; } return result; } async function getDividends() { - let result = []; + function DividendData(_index, _created, _maturity, _expiry, _amount, _claimedAmount, _name, _tokenSymbol, _tokenDecimals) { + this.index = _index; + this.created = _created; + this.maturity = _maturity; + this.expiry = _expiry; + this.amount = _amount; + this.claimedAmount = _claimedAmount; + this.name = _name; + this.tokenSymbol = _tokenSymbol; + this.tokenDecimals = _tokenDecimals; + } - let currentCheckpoint = await securityToken.methods.currentCheckpointId().call(); - for (let index = 1; index <= currentCheckpoint; index++) { - let dividendIndexes = await currentDividendsModule.methods.getDividendIndex(index).call(); - for (const i of dividendIndexes) { - let dividend = await currentDividendsModule.methods.dividends(i).call(); - dividend.index = i; - result.push(dividend); + let dividends = []; + let dividendsData = await currentDividendsModule.methods.getDividendsData().call(); + let createdArray = dividendsData.createds; + let maturityArray = dividendsData.maturitys; + let expiryArray = dividendsData.expirys; + let amountArray = dividendsData.amounts; + let claimedAmountArray = dividendsData.claimedAmounts; + let nameArray = dividendsData.names; + for (let i = 0; i < nameArray.length; i++) { + let tokenSymbol = 'ETH'; + let dividendTokenDecimals = 18; + if (dividendsType === 'ERC20') { + let tokenAddress = await currentDividendsModule.methods.dividendTokens(i).call(); + tokenSymbol = await getERC20TokenSymbol(tokenAddress); + let erc20token = new web3.eth.Contract(abis.erc20(), tokenAddress); + dividendTokenDecimals = await erc20token.methods.decimals().call(); } + dividends.push( + new DividendData( + i, + createdArray[i], + maturityArray[i], + expiryArray[i], + amountArray[i], + claimedAmountArray[i], + web3.utils.hexToUtf8(nameArray[i]), + tokenSymbol, + dividendTokenDecimals + ) + ); } - return result; + return dividends; } function getExcludedFromDataFile() { - let excludedFromFile = require('fs').readFileSync('./CLI/data/dividendsExclusions_data.csv').toString().split("\n"); - let excluded = excludedFromFile.filter(function (address) { - return web3.utils.isAddress(address); - }); - return excluded; + let parsedData = csvParse(EXCLUSIONS_DATA_CSV); + 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, validData.length); + let [data] = common.transposeBatches(batches); + + return data; } function showExcluded(excluded) { - if (excluded.length > 0) { - console.log('Current default excluded addresses:') - excluded.map(function (address) { console.log(' ', address) }); + console.log('Current default excluded addresses:') + excluded.map(address => console.log(address)); + console.log(); +} + +async function getAllModulesByType(type) { + function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused) { + this.name = _name; + this.type = _moduleType; + this.address = _address; + this.factoryAddress = _factoryAddress; + this.archived = _archived; + this.paused = _paused; + } + + let modules = []; + + let allModules = await securityToken.methods.getModulesByType(type).call(); + + for (let i = 0; i < allModules.length; i++) { + let details = await securityToken.methods.getModule(allModules[i]).call(); + let nameTemp = web3.utils.hexToUtf8(details[0]); + let pausedTemp = null; + if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER) { + let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname} /../../ build / contracts / ${nameTemp}.json`).toString()).abi; + let contractTemp = new web3.eth.Contract(abiTemp, details[1]); + pausedTemp = await contractTemp.methods.paused().call(); + } + modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp)); + } + + return modules; +} + +async function initialize(_tokenSymbol) { + welcome(); + await setup(); + if (typeof _tokenSymbol === 'undefined') { + tokenSymbol = await selectToken(); } else { - console.log('There are not default excluded addresses.') + tokenSymbol = _tokenSymbol; } - console.log(); + let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); + if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { + console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + process.exit(0); + } + let securityTokenABI = abis.securityToken(); + securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); + securityToken.setProvider(web3.currentProvider); +} + +function welcome() { + common.logAsciiBull(); + console.log("**********************************************"); + console.log("Welcome to the Command-Line Dividends Manager."); + console.log("**********************************************"); + console.log("Issuer Account: " + Issuer.address + "\n"); +} + +async function setup() { + try { + let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); + let securityTokenRegistryABI = abis.securityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + securityTokenRegistry.setProvider(web3.currentProvider); + + let polyTokenAddress = await contracts.polyToken(); + let polyTokenABI = abis.polyToken(); + polyToken = new web3.eth.Contract(polyTokenABI, polyTokenAddress); + polyToken.setProvider(web3.currentProvider); + + let moduleRegistryAddress = await contracts.moduleRegistry(); + let moduleRegistryABI = abis.moduleRegistry(); + moduleRegistry = new web3.eth.Contract(moduleRegistryABI, moduleRegistryAddress); + moduleRegistry.setProvider(web3.currentProvider); + } catch (err) { + console.log(err) + console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); + process.exit(0); + } +} + +async function selectToken() { + let result = null; + + let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); + let tokenDataArray = await Promise.all(userTokens.map(async function (t) { + let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); + return { symbol: tokenData[0], address: t }; + })); + let options = tokenDataArray.map(function (t) { + return `${t.symbol} - Deployed at ${t.address} `; + }); + options.push('Enter token symbol manually'); + + let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' }); + let selected = index != -1 ? options[index] : 'Exit'; + switch (selected) { + case 'Enter token symbol manually': + result = readlineSync.question('Enter the token symbol: '); + break; + case 'Exit': + process.exit(); + break; + default: + result = tokenDataArray[index].symbol; + break; + } + + return result; +} + +async function getERC20TokenSymbol(tokenAddress) { + let tokenSymbol = null; + try { + let erc20token = new web3.eth.Contract(abis.erc20(), tokenAddress); + tokenSymbol = await erc20token.methods.symbol().call(); + } catch (err) { + try { + // Some ERC20 tokens use bytes32 for symbol instead of string + let erc20token = new web3.eth.Contract(abis.alternativeErc20(), tokenAddress); + tokenSymbol = web3.utils.hexToUtf8(await erc20token.methods.symbol().call()); + } catch (err) { + } + } + return tokenSymbol; } module.exports = { - executeApp: async function(type, remoteNetwork) { - return executeApp(type, remoteNetwork); + executeApp: async function (_tokenSymbol) { + await initialize(_tokenSymbol); + return executeApp(); } } diff --git a/CLI/commands/faucet.js b/CLI/commands/faucet.js index 556ffa402..70eadd57b 100644 --- a/CLI/commands/faucet.js +++ b/CLI/commands/faucet.js @@ -1,7 +1,6 @@ var readlineSync = require('readline-sync'); var BigNumber = require('bignumber.js'); var common = require('./common/common_functions'); -var global = require('./common/global'); var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis') var chalk = require('chalk'); @@ -11,8 +10,7 @@ var chalk = require('chalk'); let polyToken; let usdToken; -async function executeApp(beneficiary, amount, remoteNetwork) { - await global.initialize(remoteNetwork); +async function executeApp(beneficiary, amount) { console.log("\n"); console.log("***************************") @@ -79,7 +77,7 @@ async function send_poly(beneficiary, amount) { beneficiary = readlineSync.question(`Enter beneficiary 10K USD Tokens ('${Issuer.address}'): `); if (beneficiary == "") beneficiary = Issuer.address; let getTokensAction = usdToken.methods.getTokens(web3.utils.toWei('10000'), beneficiary); - await common.sendTransaction(Issuer, getTokensAction, defaultGasPrice); + await common.sendTransaction(getTokensAction); let balance = await usdToken.methods.balanceOf(beneficiary).call(); let balanceInPoly = new BigNumber(balance).dividedBy(new BigNumber(10).pow(18)); console.log(chalk.green(`Congratulations! balance of ${beneficiary} address is ${balanceInPoly.toNumber()} USD Tokens`)); @@ -97,7 +95,7 @@ async function send_poly(beneficiary, amount) { async function transferTokens(to, amount) { try { let getTokensAction = polyToken.methods.getTokens(amount, to); - await common.sendTransaction(Issuer, getTokensAction, defaultGasPrice); + await common.sendTransaction(getTokensAction); } catch (err){ console.log(err.message); return; @@ -108,7 +106,7 @@ async function transferTokens(to, amount) { } module.exports = { - executeApp: async function(beneficiary, amount, remoteNetwork) { - return executeApp(beneficiary, amount, remoteNetwork); + executeApp: async function(beneficiary, amount) { + return executeApp(beneficiary, amount); } } \ No newline at end of file diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index b6c58cc38..2cf7b113d 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -7,6 +7,12 @@ let stoInterfaceABI; let cappedSTOABI; let usdTieredSTOABI; let generalTransferManagerABI; +let manualApprovalTransferManagerABI; +let blacklistTransferManagerABI; +let countTransferManagerABI; +let percentageTransferManagerABI; +let lockUpTransferManagerABI; +let volumeRestrictionTMABI; let generalPermissionManagerABI; let polyTokenABI; let cappedSTOFactoryABI; @@ -15,29 +21,41 @@ let erc20DividendCheckpointABI; let etherDividendCheckpointABI; let moduleInterfaceABI; let ownableABI; +let stoABI; +let iTransferManagerABI; let moduleFactoryABI; +let erc20ABI; try { - polymathRegistryABI = JSON.parse(require('fs').readFileSync('./build/contracts/PolymathRegistry.json').toString()).abi; - securityTokenRegistryABI = JSON.parse(require('fs').readFileSync('./build/contracts/SecurityTokenRegistry.json').toString()).abi; - featureRegistryABI = JSON.parse(require('fs').readFileSync('./build/contracts/FeatureRegistry.json').toString()).abi; - moduleRegistryABI = JSON.parse(require('fs').readFileSync('./build/contracts/ModuleRegistry.json').toString()).abi; - securityTokenABI = JSON.parse(require('fs').readFileSync('./build/contracts/SecurityToken.json').toString()).abi; - stoInterfaceABI = JSON.parse(require('fs').readFileSync('./build/contracts/ISTO.json').toString()).abi; - cappedSTOABI = JSON.parse(require('fs').readFileSync('./build/contracts/CappedSTO.json').toString()).abi; - usdTieredSTOABI = JSON.parse(require('fs').readFileSync('./build/contracts/USDTieredSTO.json').toString()).abi; - generalTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/GeneralTransferManager.json').toString()).abi; - generalPermissionManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/GeneralPermissionManager.json').toString()).abi; - polyTokenABI = JSON.parse(require('fs').readFileSync('./build/contracts/PolyTokenFaucet.json').toString()).abi; - cappedSTOFactoryABI = JSON.parse(require('fs').readFileSync('./build/contracts/CappedSTOFactory.json').toString()).abi; - usdTieredSTOFactoryABI = JSON.parse(require('fs').readFileSync('./build/contracts/USDTieredSTOFactory.json').toString()).abi; - erc20DividendCheckpointABI = JSON.parse(require('fs').readFileSync('./build/contracts/ERC20DividendCheckpoint.json').toString()).abi; - etherDividendCheckpointABI = JSON.parse(require('fs').readFileSync('./build/contracts/EtherDividendCheckpoint.json').toString()).abi; - moduleInterfaceABI = JSON.parse(require('fs').readFileSync('./build/contracts/IModule.json').toString()).abi; - ownableABI = JSON.parse(require('fs').readFileSync('./build/contracts/Ownable.json').toString()).abi; - moduleFactoryABI = JSON.parse(require('fs').readFileSync('./build/contracts/ModuleFactory.json').toString()).abi; + polymathRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolymathRegistry.json`).toString()).abi; + securityTokenRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/SecurityTokenRegistry.json`).toString()).abi; + featureRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/FeatureRegistry.json`).toString()).abi; + moduleRegistryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ModuleRegistry.json`).toString()).abi; + securityTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/SecurityToken.json`).toString()).abi; + stoInterfaceABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ISTO.json`).toString()).abi; + cappedSTOABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/CappedSTO.json`).toString()).abi; + usdTieredSTOABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/USDTieredSTO.json`).toString()).abi; + generalTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/GeneralTransferManager.json`).toString()).abi; + manualApprovalTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ManualApprovalTransferManager.json`).toString()).abi; + countTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/CountTransferManager.json`).toString()).abi; + percentageTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PercentageTransferManager.json`).toString()).abi; + blacklistTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/BlacklistTransferManager.json`).toString()).abi; + volumeRestrictionTMABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/VolumeRestrictionTM.json`).toString()).abi; + lockUpTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/LockUpTransferManager.json`).toString()).abi; + generalPermissionManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/GeneralPermissionManager.json`).toString()).abi; + polyTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolyTokenFaucet.json`).toString()).abi; + cappedSTOFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/CappedSTOFactory.json`).toString()).abi; + usdTieredSTOFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/USDTieredSTOFactory.json`).toString()).abi; + erc20DividendCheckpointABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ERC20DividendCheckpoint.json`).toString()).abi; + etherDividendCheckpointABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/EtherDividendCheckpoint.json`).toString()).abi; + moduleInterfaceABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/IModule.json`).toString()).abi; + ownableABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/Ownable.json`).toString()).abi; + stoABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/STO.json`).toString()).abi + iTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ITransferManager.json`).toString()).abi + moduleFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ModuleFactory.json`).toString()).abi; + erc20ABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/DetailedERC20.json`).toString()).abi; } catch (err) { - console.log('\x1b[31m%s\x1b[0m',"Couldn't find contracts' artifacts. Make sure you ran truffle compile first"); + console.log('\x1b[31m%s\x1b[0m', "Couldn't find contracts' artifacts. Make sure you ran truffle compile first"); throw err; } @@ -69,6 +87,24 @@ module.exports = { generalTransferManager: function () { return generalTransferManagerABI; }, + manualApprovalTransferManager: function () { + return manualApprovalTransferManagerABI; + }, + blacklistTransferManager: function () { + return blacklistTransferManagerABI; + }, + countTransferManager: function () { + return countTransferManagerABI; + }, + percentageTransferManager: function () { + return percentageTransferManagerABI; + }, + lockUpTransferManager: function () { + return lockUpTransferManagerABI; + }, + volumeRestrictionTM: function () { + return volumeRestrictionTMABI; + }, generalPermissionManager: function () { return generalPermissionManagerABI; }, @@ -93,7 +129,28 @@ module.exports = { ownable: function () { return ownableABI; }, + sto: function () { + return stoABI; + }, + ITransferManager: function () { + return iTransferManagerABI; + }, moduleFactory: function () { return moduleFactoryABI; + }, + erc20: function () { + return erc20ABI; + }, + alternativeErc20: function () { + let alternativeErc20 = [{ + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [{ "name": "", "type": "bytes32" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }]; + return alternativeErc20; } } \ No newline at end of file diff --git a/CLI/commands/helpers/contract_addresses.js b/CLI/commands/helpers/contract_addresses.js index 59a2f39c4..79f5cba10 100644 --- a/CLI/commands/helpers/contract_addresses.js +++ b/CLI/commands/helpers/contract_addresses.js @@ -13,7 +13,7 @@ function getPolymathRegistryAddress(networkId) { result = ""; break; case 15: // GANACHE - result = JSON.parse(require('fs').readFileSync('./build/contracts/PolymathRegistry.json').toString()).networks[networkId].address; + result = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolymathRegistry.json`).toString()).networks[networkId].address; break; case 42: // KOVAN result = "0x5b215a7d39ee305ad28da29bf2f0425c6c2a00b3"; @@ -52,35 +52,35 @@ module.exports = { let networkId = await web3.eth.net.getId(); return getPolymathRegistryAddress(networkId); }, - securityTokenRegistry: async function() { + securityTokenRegistry: async function () { let polymathRegistry = await getPolymathRegistry(); return await polymathRegistry.methods.getAddress("SecurityTokenRegistry").call(); }, - moduleRegistry: async function() { + moduleRegistry: async function () { let polymathRegistry = await getPolymathRegistry(); return await polymathRegistry.methods.getAddress("ModuleRegistry").call(); }, - featureRegistry: async function() { + featureRegistry: async function () { let polymathRegistry = await getPolymathRegistry(); return await polymathRegistry.methods.getAddress("FeatureRegistry").call(); }, - polyToken: async function() { + polyToken: async function () { let polymathRegistry = await getPolymathRegistry(); return await polymathRegistry.methods.getAddress("PolyToken").call(); }, - usdToken: async function() { + usdToken: async function () { let networkId = await web3.eth.net.getId(); if (networkId == 1) return "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"; else if (networkId == 42) return "0xc4375b7de8af5a38a93548eb8453a498222c4ff2"; else - return JSON.parse(require('fs').readFileSync('./build/contracts/PolyTokenFaucet.json').toString()).networks[networkId].address; + return JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolyTokenFaucet.json`).toString()).networks[networkId].address; }, - getModuleFactoryAddressByName: async function(stAddress, moduleType, moduleName) { + getModuleFactoryAddressByName: async function (stAddress, moduleType, moduleName) { let moduleRegistry = await getModuleRegistry(); let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(moduleType, stAddress).call(); - + let result = null; let counter = 0; let moduleFactoryABI = abis.moduleFactory(); diff --git a/CLI/commands/helpers/csv.js b/CLI/commands/helpers/csv.js new file mode 100644 index 000000000..a36dcec15 --- /dev/null +++ b/CLI/commands/helpers/csv.js @@ -0,0 +1,35 @@ +const csvParse = require('csv-parse/lib/sync'); +const fs = require('fs'); +const web3Utils = require('web3-utils'); + +function _cast(obj) { + if (/^(\-|\+)?([1-9]+[0-9]*)$/.test(obj)) { // Int + obj = parseInt(obj); + } + else if (/^[+-]?([0-9]*[.])?[0-9]+$/.test(obj)) { // Float + obj = parseFloat(obj); + } + else if (/^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})$/.test(obj)) { // Datetime + var matches = /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})$/.exec(obj); + var composedDate = new Date(matches[3], matches[1] - 1, matches[2]); + var timestampDate = composedDate.getTime(); + obj = timestampDate / 1000; + } + else if (obj.toLowerCase() === "true" || obj.toLowerCase() === "false") { // Boolean + obj = JSON.parse(obj.toLowerCase()); + } else if (web3Utils.isAddress(obj)) { + obj = web3Utils.toChecksumAddress(obj); + } + return obj; +} + +function parse(_csvFilePath, _batchSize) { + // Read file + let input = fs.readFileSync(_csvFilePath); + // Parse csv + let data = csvParse(input, { cast: _cast }); + + return data; +} + +module.exports = parse; \ No newline at end of file diff --git a/CLI/commands/helpers/time.js b/CLI/commands/helpers/time.js new file mode 100644 index 000000000..294f54f67 --- /dev/null +++ b/CLI/commands/helpers/time.js @@ -0,0 +1,94 @@ +const moment = require('moment'); +const chalk = require('chalk'); + +function increaseTime(duration) { + const id = Date.now(); + + return new Promise((resolve, reject) => { + web3.currentProvider.send( + { + jsonrpc: "2.0", + method: "evm_increaseTime", + params: [duration], + id: id + }, + err1 => { + if (err1) return reject(err1); + + web3.currentProvider.send( + { + jsonrpc: "2.0", + method: "evm_mine", + id: id + 1 + }, + (err2, res) => { + return err2 ? reject(err2) : resolve(res); + } + ); + } + ); + }); +} + +function jumpToTime(timestamp) { + const id = Date.now(); + + return new Promise((resolve, reject) => { + web3.currentProvider.send( + { + jsonrpc: "2.0", + method: "evm_mine", + params: [timestamp], + id: id + }, + (err, res) => { + return err ? reject(err) : resolve(res); + } + ); + }); +} + +async function increaseTimeByDate(toTime) { + if (await web3.eth.net.getId() === 15) { + if (toTime.isValid()) { + let currentTime = (await web3.eth.getBlock('latest')).timestamp; + if (toTime.unix() > currentTime) { + await jumpToTime(toTime.unix()); + currentTime = (await web3.eth.getBlock('latest')).timestamp; + console.log(chalk.green(`Current datetime is ${currentTime} or ${moment.unix(currentTime).format("MM/DD/YYYY HH:mm:ss")}`)); + } else { + console.log(chalk.red(`It is not possible to go back in time. Please try again with a time in the future to travel to`)); + } + } else { + console.log(chalk.red(`Date format is not valid. Please use a valid Unix epoch time`)); + } + } else { + console.log(chalk.red(`Time traveling is only possible over develpment network`)); + } +} + +async function increaseTimeByDuration(duration) { + if (await web3.eth.net.getId() === 15) { + if (duration > 0) { + await increaseTime(duration); + currentTime = (await web3.eth.getBlock('latest')).timestamp; + console.log(chalk.green(`Current datetime is ${currentTime} or ${moment.unix(currentTime).format("MM/DD/YYYY HH:mm:ss")}`)); + } else { + console.log(chalk.red(`It is not possible to go back in time. Please try again with a time in the future to travel to`)); + } + } else { + console.log(chalk.red(`Time traveling is only possible over develpment network`)); + } +} + +function increaseTimeToDate(date) { + var toDate = moment(date, ['MM-DD-YYYY', 'MM-DD-YYYY HH:mm:ss']); + return increaseTimeByDate(toDate); +} + +function increaseTimeToEpochDate(epochDate) { + var toTime = moment.unix(epochDate); + return increaseTimeByDate(toTime); +} + +module.exports = { increaseTimeByDuration, increaseTimeToDate, increaseTimeToEpochDate }; diff --git a/CLI/commands/investor_portal.js b/CLI/commands/investor_portal.js index 9a5229a7c..124d0d297 100644 --- a/CLI/commands/investor_portal.js +++ b/CLI/commands/investor_portal.js @@ -3,25 +3,21 @@ var readlineSync = require('readline-sync'); var BigNumber = require('bignumber.js'); var chalk = require('chalk'); var common = require('./common/common_functions'); -var global = require('./common/global'); +var gbl = require('./common/global'); // Load Contract artifacts var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis'); -const STO_KEY = 3; -const FUND_RAISE_TYPES = { - ETH: 0, - POLY: 1, - DAI: 2 -} +const ETH = 'ETH'; +const POLY = 'POLY'; +const STABLE = 'STABLE'; let securityTokenRegistry; let securityToken; let selectedSTO; let currentSTO; let polyToken; -let usdToken; let generalTransferManager; let raiseTypes = []; @@ -39,8 +35,7 @@ let displayCanBuy; let displayValidKYC; // Start Script -async function executeApp(investorAddress, investorPrivKey, symbol, currency, amount, remoteNetwork) { - await global.initialize(remoteNetwork); +async function executeApp(investorAddress, investorPrivKey, symbol, currency, amount) { common.logAsciiBull(); console.log("********************************************"); @@ -56,24 +51,25 @@ async function executeApp(investorAddress, investorPrivKey, symbol, currency, am } } if (investorAddress != "") { - User = { address: investorAddress, privateKey: investorPrivKey}; + User = { address: investorAddress, privateKey: investorPrivKey }; } else { User = Issuer; } try { await inputSymbol(symbol); - await showUserInfo(User.address); switch (selectedSTO) { case 'CappedSTO': let cappedSTOABI = abis.cappedSTO(); currentSTO = new web3.eth.Contract(cappedSTOABI, STOAddress); + await showUserInfo(User.address); await showCappedSTOInfo(); await investCappedSTO(currency, amount); break; case 'USDTieredSTO': let usdTieredSTOABI = abis.usdTieredSTO(); currentSTO = new web3.eth.Contract(usdTieredSTOABI, STOAddress); + await showUserInfo(User.address); await showUserInfoForUSDTieredSTO(); await showUSDTieredSTOInfo(); await investUsdTieredSTO(currency, amount) @@ -95,10 +91,6 @@ async function setup() { let polytokenABI = abis.polyToken(); polyToken = new web3.eth.Contract(polytokenABI, polytokenAddress); polyToken.setProvider(web3.currentProvider); - - let usdTokenAddress = await contracts.usdToken(); - usdToken = new web3.eth.Contract(polytokenABI, usdTokenAddress); - usdToken.setProvider(web3.currentProvider); } catch (err) { console.log(err); console.log(chalk.red(`There was a problem getting the contracts. Make sure they are deployed to the selected network.`)); @@ -117,7 +109,7 @@ async function inputSymbol(symbol) { if (STSymbol == "") process.exit(); STAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(STSymbol).call(); - if (STAddress == "0x0000000000000000000000000000000000000000"){ + if (STAddress == "0x0000000000000000000000000000000000000000") { console.log(`Token symbol provided is not a registered Security Token. Please enter another symbol.`); } else { let securityTokenABI = abis.securityToken(); @@ -129,12 +121,12 @@ async function inputSymbol(symbol) { let generalTransferManagerABI = abis.generalTransferManager(); generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, gtmModule[0]); - let stoModules = await securityToken.methods.getModulesByType(STO_KEY).call(); + let stoModules = await securityToken.methods.getModulesByType(gbl.constants.MODULES_TYPES.STO).call(); if (stoModules.length == 0) { console.log(chalk.red(`There is no STO module attached to the ${STSymbol.toUpperCase()} Token. No further actions can be taken.`)); process.exit(0); } else { - STOAddress = stoModules[0]; + STOAddress = stoModules[0]; let stoModuleData = await securityToken.methods.getModule(STOAddress).call(); selectedSTO = web3.utils.toAscii(stoModuleData[0]).replace(/\u0000/g, ''); let interfaceSTOABI = abis.stoInterface(); @@ -163,14 +155,18 @@ async function showUserInfo(_user) { console.log(` ******************* User Information ******************** - Address: ${_user}`); - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES.POLY)) { + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.POLY).call()) { console.log(` - POLY balance:\t ${await polyBalance(_user)}`); } - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES.ETH)) { + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.ETH).call()) { console.log(` - ETH balance:\t ${web3.utils.fromWei(await web3.eth.getBalance(_user))}`); } - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES.DAI)) { - console.log(` - DAI balance:\t ${await usdBalance(_user)}`); + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.STABLE).call()) { + let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); + let stableSymbolsAndBalance = await processAddressWithBalance(listOfStableCoins); + stableSymbolsAndBalance.forEach(stable => { + console.log(` - ${stable.symbol} balance:\t ${web3.utils.fromWei(stable.balance)}`); + }); } } @@ -185,27 +181,27 @@ async function showCappedSTOInfo() { let displayRaiseType; let displayFundsRaised; - for (const fundType in FUND_RAISE_TYPES) { - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES[fundType]).call()) { + for (const fundType in gbl.constants.FUND_RAISE_TYPES) { + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES[fundType]).call()) { raiseTypes.push(fundType); displayRaiseType = fundType; - displayFundsRaised = await currentSTO.methods.fundsRaised(FUND_RAISE_TYPES[fundType]).call(); + displayFundsRaised = await currentSTO.methods.fundsRaised(gbl.constants.FUND_RAISE_TYPES[fundType]).call(); } } - let now = Math.floor(Date.now()/1000); + let now = Math.floor(Date.now() / 1000); - await generalTransferManager.methods.whitelist(User.address).call({}, function(error, result){ + await generalTransferManager.methods.whitelist(User.address).call({}, function (error, result) { displayCanBuy = result.canBuyFromSTO; displayValidKYC = parseInt(result.expiryTime) > now; }); let timeTitle; let timeRemaining; - if(now < displayStartTime){ + if (now < displayStartTime) { timeTitle = "STO starts in: "; timeRemaining = displayStartTime - now; - }else{ + } else { timeTitle = "Time remaining:"; timeRemaining = displayEndTime - now; } @@ -219,7 +215,7 @@ async function showCappedSTOInfo() { - Start Time: ${new Date(displayStartTime * 1000)} - End Time: ${new Date(displayEndTime * 1000)} - Raise Type: ${displayRaiseType} - - Rate: 1 ${displayRaiseType} = ${displayRate} ${STSymbol.toUpperCase()} + - Rate: 1 ${displayRaiseType} = ${web3.utils.fromWei(displayRate)} ${STSymbol.toUpperCase()} --------------------------------------------------------------- - ${timeTitle} ${timeRemaining} - Funds raised: ${web3.utils.fromWei(displayFundsRaised)} ${displayRaiseType} @@ -228,7 +224,7 @@ async function showCappedSTOInfo() { - Investor count: ${displayInvestorCount} `); - if(!displayCanBuy) { + if (!displayCanBuy) { console.log(chalk.red(`Your address is not approved to participate in this token sale.\n`)); process.exit(0); } else if (!displayValidKYC) { @@ -243,30 +239,77 @@ async function showCappedSTOInfo() { } } -async function showUserInfoForUSDTieredSTO() -{ - for (const fundType in FUND_RAISE_TYPES) { - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES[fundType]).call()) { - let displayInvestorInvested = web3.utils.fromWei(await currentSTO.methods.investorInvested(User.address, FUND_RAISE_TYPES[fundType]).call()); - console.log(` - Invested in ${fundType}:\t ${displayInvestorInvested} ${fundType}`); +async function processAddressWithBalance(array) { + let list = []; + for (const address of array) { + let symbol = await checkSymbol(address); + let balance = await checkBalance(address); + list.push({ 'address': address, 'symbol': symbol, 'balance': balance }) + } + return list +} + +async function processAddress(array) { + let list = []; + for (const address of array) { + let symbol = await checkSymbol(address); + list.push({ "symbol": symbol, "address": address }) + } + return list +} + +async function checkSymbol(address) { + let stableCoin = common.connect(abis.erc20(), address); + try { + return await stableCoin.methods.symbol().call(); + } catch (e) { + return "" + } +} + +async function checkBalance(address) { + let stableCoin = common.connect(abis.erc20(), address); + try { + return await stableCoin.methods.balanceOf(User.address).call(); + } catch (e) { + return "" + } +} + +async function showUserInfoForUSDTieredSTO() { + let stableSymbols = []; + let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); + + for (const fundType in gbl.constants.FUND_RAISE_TYPES) { + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES[fundType]).call()) { + if (fundType == STABLE) { + stableSymbols = await processAddress(listOfStableCoins) + } + let displayInvestorInvested = web3.utils.fromWei(await currentSTO.methods.investorInvested(User.address, gbl.constants.FUND_RAISE_TYPES[fundType]).call()); + if ((fundType == STABLE) && (stableSymbols.length)) { + console.log(` - Invested in stable coin(s): ${displayInvestorInvested} USD`); + } else { + console.log(` - Invested in ${fundType}:\t ${displayInvestorInvested} ${fundType}`); + } } } let displayInvestorInvestedUSD = web3.utils.fromWei(await currentSTO.methods.investorInvestedUSD(User.address).call()); - console.log(` - Invested in USD: ${displayInvestorInvestedUSD} USD`); + console.log(` - Total invested in USD: ${displayInvestorInvestedUSD} USD`); - await generalTransferManager.methods.whitelist(User.address).call({}, function(error, result){ + await generalTransferManager.methods.whitelist(User.address).call({}, function (error, result) { displayCanBuy = result.canBuyFromSTO; - displayValidKYC = parseInt(result.expiryTime) > Math.floor(Date.now()/1000); + displayValidKYC = parseInt(result.expiryTime) > Math.floor(Date.now() / 1000); }); - console.log(` - Whitelisted: ${(displayCanBuy)? 'YES' : 'NO'}`); - console.log(` - Valid KYC: ${(displayValidKYC)? 'YES' : 'NO'}`); + console.log(` - Whitelisted: ${(displayCanBuy) ? 'YES' : 'NO'}`); + console.log(` - Valid KYC: ${(displayValidKYC) ? 'YES' : 'NO'}`); - let displayIsUserAccredited = await currentSTO.methods.accredited(User.address).call(); - console.log(` - Accredited: ${(displayIsUserAccredited)? "YES" : "NO"}`) + let investorData = await currentSTO.methods.investors(User.address).call(); + let displayIsUserAccredited = investorData.accredited == 1; + console.log(` - Accredited: ${(displayIsUserAccredited) ? "YES" : "NO"}`) - if (!await currentSTO.methods.accredited(User.address).call()) { - let displayOverrideNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSDOverride(User.address).call()) + if (!displayIsUserAccredited) { + let displayOverrideNonAccreditedLimitUSD = web3.utils.fromWei(investorData.nonAccreditedLimitUSDOverride); let displayNonAccreditedLimitUSD = displayOverrideNonAccreditedLimitUSD != 0 ? displayOverrideNonAccreditedLimitUSD : web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call()); let displayTokensRemainingAllocation = displayNonAccreditedLimitUSD - displayInvestorInvestedUSD; console.log(` - Remaining allocation: ${(displayTokensRemainingAllocation > 0 ? displayTokensRemainingAllocation : 0)} USD`); @@ -286,51 +329,62 @@ async function showUSDTieredSTOInfo() { let displayIsOpen = await currentSTO.methods.isOpen().call(); let displayTokenSymbol = await securityToken.methods.symbol().call(); let tiersLength = await currentSTO.methods.getNumberOfTiers().call(); + let stableSymbols = []; + let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); - for (const fundType in FUND_RAISE_TYPES) { - if (await currentSTO.methods.fundRaiseTypes(FUND_RAISE_TYPES[fundType]).call()) { + for (const fundType in gbl.constants.FUND_RAISE_TYPES) { + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES[fundType]).call()) { raiseTypes.push(fundType); + if (fundType == STABLE) { + stableSymbols = await processAddress(listOfStableCoins) + } } } let displayTiers = ""; let displayMintedPerTier = ""; for (let t = 0; t < tiersLength; t++) { - let ratePerTier = await currentSTO.methods.ratePerTier(t).call(); - let tokensPerTierTotal = await currentSTO.methods.tokensPerTierTotal(t).call(); - let mintedPerTierTotal = await currentSTO.methods.mintedPerTierTotal(t).call(); + let tier = await currentSTO.methods.tiers(t).call(); + let ratePerTier = tier.rate; + let tokensPerTierTotal = tier.tokenTotal; + let mintedPerTierTotal = tier.mintedTotal; + let mintedPerTierPerRaiseType = await currentSTO.methods.getTokensMintedByTier(t).call(); let displayMintedPerTierPerType = ""; let displayDiscountTokens = ""; for (const type of raiseTypes) { let displayDiscountMinted = ""; - if (type == 'POLY') { - let tokensPerTierDiscountPoly = await currentSTO.methods.tokensPerTierDiscountPoly(t).call(); - if (tokensPerTierDiscountPoly > 0) { - let ratePerTierDiscountPoly = await currentSTO.methods.ratePerTierDiscountPoly(t).call(); - let mintedPerTierDiscountPoly = await currentSTO.methods.mintedPerTierDiscountPoly(t).call(); - displayDiscountTokens = ` + let tokensPerTierDiscountPoly = tier.tokensDiscountPoly; + if (tokensPerTierDiscountPoly > 0) { + let ratePerTierDiscountPoly = tier.rateDiscountPoly; + let mintedPerTierDiscountPoly = tier.mintedDiscountPoly; + displayDiscountTokens = ` Tokens at discounted rate: ${web3.utils.fromWei(tokensPerTierDiscountPoly)} ${displayTokenSymbol} Discounted rate: ${web3.utils.fromWei(ratePerTierDiscountPoly, 'ether')} USD per Token`; - displayDiscountMinted = `(${web3.utils.fromWei(mintedPerTierDiscountPoly)} ${displayTokenSymbol} at discounted rate)`; - } + displayDiscountMinted = `(${web3.utils.fromWei(mintedPerTierDiscountPoly)} ${displayTokenSymbol} at discounted rate)`; } - let mintedPerTier = await currentSTO.methods.mintedPerTier(FUND_RAISE_TYPES[type], t).call(); - displayMintedPerTierPerType += ` + + let mintedPerTier = mintedPerTierPerRaiseType[gbl.constants.FUND_RAISE_TYPES[type]]; + if ((type == STABLE) && (stableSymbols.length)) { + displayMintedPerTierPerType += ` + Sold for stable coin(s): ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; + } else { + displayMintedPerTierPerType += ` Sold for ${type}:\t\t ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; + } } displayTiers += ` - - Tier ${t+1}: + - Tier ${t + 1}: Tokens: ${web3.utils.fromWei(tokensPerTierTotal)} ${displayTokenSymbol} Rate: ${web3.utils.fromWei(ratePerTier)} USD per Token` - + displayDiscountTokens; + + displayDiscountTokens; - displayMintedPerTier += ` - - Tokens minted in Tier ${t+1}: ${web3.utils.fromWei(mintedPerTierTotal)} ${displayTokenSymbol}` - + displayMintedPerTierPerType; + displayMintedPerTier += ` + - Tokens minted in Tier ${t + 1}: ${web3.utils.fromWei(mintedPerTierTotal)} ${displayTokenSymbol}` + + displayMintedPerTierPerType; } let displayFundsRaisedUSD = web3.utils.fromWei(await currentSTO.methods.fundsRaisedUSD().call()); @@ -338,21 +392,37 @@ async function showUSDTieredSTOInfo() { let displayFundsRaisedPerType = ''; let displayTokensSoldPerType = ''; for (const type of raiseTypes) { - let fundsRaised = web3.utils.fromWei(await currentSTO.methods.fundsRaised(FUND_RAISE_TYPES[type]).call()); - displayFundsRaisedPerType += ` + let fundsRaised = web3.utils.fromWei(await currentSTO.methods.fundsRaised(gbl.constants.FUND_RAISE_TYPES[type]).call()); + if ((type == STABLE) && (stableSymbols.length)) { + stableSymbols.forEach(async (stable) => { + let raised = await getStableCoinsRaised(currentSTO, stable.address); + displayFundsRaisedPerType += ` + ${stable.symbol}:\t\t\t ${web3.utils.fromWei(raised)} ${stable.symbol}`; + }) + } else { + displayFundsRaisedPerType += ` ${type}:\t\t\t ${fundsRaised} ${type}`; - + } //Only show sold per raise type is more than one are allowed if (raiseTypes.length > 1) { - let tokensSoldPerType = web3.utils.fromWei(await currentSTO.methods.getTokensSoldFor(FUND_RAISE_TYPES[type]).call()); - displayTokensSoldPerType += ` + let tokensSoldPerType = web3.utils.fromWei(await currentSTO.methods.getTokensSoldFor(gbl.constants.FUND_RAISE_TYPES[type]).call()); + if ((type == STABLE) && (stableSymbols.length)) { + displayTokensSoldPerType += ` + Sold for stable coin(s): ${tokensSoldPerType} ${displayTokenSymbol}`; + } else { + displayTokensSoldPerType += ` Sold for ${type}:\t\t ${tokensSoldPerType} ${displayTokenSymbol}`; + } } } let displayRaiseType = raiseTypes.join(' - '); + //If STO has stable coins, we list them one by one + if (stableSymbols.length) { + displayRaiseType = displayRaiseType.replace(STABLE, "") + `${stableSymbols.map((obj) => { return obj.symbol }).toString().replace(`,`, ` - `)}` + } - let now = Math.floor(Date.now()/1000); + let now = Math.floor(Date.now() / 1000); let timeTitle; let timeRemaining; if (now < displayStartTime) { @@ -371,19 +441,19 @@ async function showUSDTieredSTOInfo() { - End Time: ${new Date(displayEndTime * 1000)} - Raise Type: ${displayRaiseType} - Tiers: ${tiersLength}` - + displayTiers + ` + + displayTiers + ` - Minimum Investment: ${displayMinimumInvestmentUSD} USD - Default NonAccredited Limit: ${displayNonAccreditedLimitUSD} USD ----------------------------------------------------------------------- - ${timeTitle} ${timeRemaining} - Tokens Sold: ${displayTokensSold} ${displayTokenSymbol}` - + displayTokensSoldPerType + ` + + displayTokensSoldPerType + ` - Current Tier: ${displayCurrentTier}` - + displayMintedPerTier + ` + + displayMintedPerTier + ` - Investor count: ${displayInvestorCount} - Funds Raised` - + displayFundsRaisedPerType + ` - USD: ${displayFundsRaisedUSD} USD + + displayFundsRaisedPerType + ` + Total USD: ${displayFundsRaisedUSD} USD `); if (!displayCanBuy) { @@ -401,6 +471,10 @@ async function showUSDTieredSTOInfo() { } } +async function getStableCoinsRaised(currentSTO, address) { + return await currentSTO.methods.stableCoinsRaised(address).call() +} + // Allow investor to buy tokens. async function investCappedSTO(currency, amount) { if (typeof currency !== 'undefined' && !raiseTypes.inlcudes(currency)) { @@ -416,7 +490,7 @@ async function investCappedSTO(currency, amount) { } if (amt == "") process.exit(); - let rate = await currentSTO.methods.rate().call(); + let rate = web3.utils.fromWei(await currentSTO.methods.rate().call()); let cost = new BigNumber(amt).div(rate); console.log(`This investment will cost ${cost} ${raiseTypes[0]}`); @@ -424,14 +498,14 @@ async function investCappedSTO(currency, amount) { if (raiseTypes[0] == 'POLY') { let userBalance = await polyBalance(User.address); if (parseInt(userBalance) >= parseInt(cost)) { - let allowance = await polyToken.methods.allowance(STOAddress, User.address).call(); - if (allowance < costWei) { + let allowance = await polyToken.methods.allowance(User.address, STOAddress).call(); + if (parseInt(allowance) < parseInt(costWei)) { let approveAction = polyToken.methods.approve(STOAddress, costWei); - await common.sendTransaction(User, approveAction, defaultGasPrice); + await common.sendTransaction(approveAction, { from: User }); } let actionBuyTokensWithPoly = currentSTO.methods.buyTokensWithPoly(costWei); - let receipt = await common.sendTransaction(User, actionBuyTokensWithPoly, defaultGasPrice); - logTokensPurchasedCappedSTO(receipt); + let receipt = await common.sendTransaction(actionBuyTokensWithPoly, { from: User }); + logTokensPurchasedCappedSTO(receipt, 'POLY'); } else { console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} POLY but have ${userBalance} POLY.`)); console.log(chalk.red(`Please purchase a smaller amount of tokens or access the POLY faucet to get the POLY to complete this txn.`)); @@ -439,15 +513,19 @@ async function investCappedSTO(currency, amount) { } } else { let actionBuyTokens = currentSTO.methods.buyTokens(User.address); - let receipt = await common.sendTransaction(User, actionBuyTokens, defaultGasPrice, costWei); - logTokensPurchasedCappedSTO(receipt); + let receipt = await common.sendTransaction(actionBuyTokens, { from: User, value: costWei }); + logTokensPurchasedCappedSTO(receipt, 'ETH'); } await showTokenInfo(); } // Allow investor to buy tokens. async function investUsdTieredSTO(currency, amount) { + let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); + let stableSymbols = await processAddress(listOfStableCoins); + let raiseType; + if (typeof currency !== 'undefined') { if (!raiseTypes.inlcudes(currency)) { console.log(chalk.red(`${currency} is not allowed for current STO`)); @@ -457,15 +535,31 @@ async function investUsdTieredSTO(currency, amount) { } } else { for (const type of raiseTypes) { - let displayPrice = web3.utils.fromWei(await currentSTO.methods.convertToUSD(FUND_RAISE_TYPES[type], web3.utils.toWei("1")).call()); - console.log(chalk.green(` Current ${type} price:\t\t ${displayPrice} USD`)); + let displayPrice = web3.utils.fromWei(await currentSTO.methods.convertToUSD(gbl.constants.FUND_RAISE_TYPES[type], web3.utils.toWei("1")).call()); + if (!((type == STABLE) && (stableSymbols.length))) { + console.log(chalk.green(` Current ${type} price:\t\t ${displayPrice} USD`)); + } } if (raiseTypes.length > 1) { - let index = readlineSync.keyInSelect(raiseTypes, 'Choose one of the allowed raise types: ', {cancel: false}); - raiseType = raiseTypes[index]; + const stableIndex = raiseTypes.indexOf(STABLE); + if (stableIndex > -1) { + raiseTypes.splice(stableIndex, 1) + stableSymbols.forEach((stable) => { + raiseTypes.push(stable.symbol) + }) + } + raiseType = raiseTypes[selectToken('Choose one of the allowed raise types: ')]; } else { - raiseType = raiseTypes[0]; - console.log(''); + if (raiseTypes[0] == STABLE) { + raiseTypes.splice(raiseTypes.indexOf(STABLE), 1) + stableSymbols.forEach((stable) => { + raiseTypes.push(stable.symbol) + }) + raiseType = raiseTypes[selectToken('Choose one of the allowed stable coin(s): ')]; + } else { + raiseType = raiseTypes[0]; + console.log(''); + } } } @@ -473,9 +567,17 @@ async function investUsdTieredSTO(currency, amount) { if (typeof amount === 'undefined') { let investorInvestedUSD = web3.utils.fromWei(await currentSTO.methods.investorInvestedUSD(User.address).call()); let minimumInvestmentUSD = await currentSTO.methods.minimumInvestmentUSD().call(); - let minimumInvestmentRaiseType = await currentSTO.methods.convertFromUSD(FUND_RAISE_TYPES[raiseType], minimumInvestmentUSD).call(); + let minimumInvestmentRaiseType; + + // if raiseType is different than ETH or POLY, we assume is STABLE + if ((raiseType != ETH) && (raiseType != POLY)) { + minimumInvestmentRaiseType = await currentSTO.methods.convertFromUSD(gbl.constants.FUND_RAISE_TYPES[STABLE], minimumInvestmentUSD).call(); + } else { + minimumInvestmentRaiseType = await currentSTO.methods.convertFromUSD(gbl.constants.FUND_RAISE_TYPES[raiseType], minimumInvestmentUSD).call(); + } + cost = readlineSync.question(chalk.yellow(`Enter the amount of ${raiseType} you would like to invest or press 'Enter' to exit: `), { - limit: function(input) { + limit: function (input) { return investorInvestedUSD != 0 || parseInt(input) > parseInt(web3.utils.fromWei(minimumInvestmentRaiseType)); }, limitMessage: `Amount must be greater than minimum investment (${web3.utils.fromWei(minimumInvestmentRaiseType)} ${raiseType} = ${web3.utils.fromWei(minimumInvestmentUSD)} USD)` @@ -487,41 +589,61 @@ async function investUsdTieredSTO(currency, amount) { let costWei = web3.utils.toWei(cost.toString()); - if (raiseType == 'POLY') { + let tokensToBuy; + // if raiseType is different than ETH or POLY, we assume is STABLE + if ((raiseType != ETH) && (raiseType != POLY)) { + tokensToBuy = await currentSTO.methods.buyTokensView(User.address, costWei, gbl.constants.FUND_RAISE_TYPES[STABLE]).call(); + } else { + tokensToBuy = await currentSTO.methods.buyTokensView(User.address, costWei, gbl.constants.FUND_RAISE_TYPES[raiseType]).call(); + } + + let minTokenToBuy = tokensToBuy.tokensMinted; + console.log(chalk.yellow(`You are going to spend ${web3.utils.fromWei(tokensToBuy.spentValue)} ${raiseType} (${web3.utils.fromWei(tokensToBuy.spentUSD)} USD) to buy ${web3.utils.fromWei(minTokenToBuy)} ${STSymbol} approx.`)); + console.log(chalk.yellow(`Due to ${raiseType} price changes and network delays, it is possible that the final amount of purchased tokens is lower.`)); + if (typeof amount !== 'undefined' || !readlineSync.keyInYNStrict(`Do you want the transaction to fail if this happens?`)) { + minTokenToBuy = 0; + } + + if (raiseType == POLY) { let userBalance = await polyBalance(User.address); if (parseInt(userBalance) >= parseInt(cost)) { - let allowance = await polyToken.methods.allowance(STOAddress, User.address).call(); - if (allowance < costWei) { + let allowance = await polyToken.methods.allowance(User.address, STOAddress).call(); + if (parseInt(allowance) < parseInt(costWei)) { let approveAction = polyToken.methods.approve(STOAddress, costWei); - await common.sendTransaction(User, approveAction, defaultGasPrice); + await common.sendTransaction(approveAction, { from: User }); } - let actionBuyWithPoly = currentSTO.methods.buyWithPOLY(User.address, costWei); - let receipt = await common.sendTransaction(User, actionBuyWithPoly,defaultGasPrice, 0, 2); + let actionBuyWithPoly = currentSTO.methods.buyWithPOLYRateLimited(User.address, costWei, minTokenToBuy); + let receipt = await common.sendTransaction(actionBuyWithPoly, { from: User, factor: 2 }); logTokensPurchasedUSDTieredSTO(receipt); } else { console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} POLY but have ${userBalance} POLY.`)); console.log(chalk.red(`Please purchase a smaller amount of tokens or access the POLY faucet to get the POLY to complete this txn.`)); process.exit(); } - } else if (raiseType == 'DAI') { - let userBalance = await usdBalance(User.address); - if (parseInt(userBalance) >= parseInt(cost)) { - let allowance = await usdToken.methods.allowance(STOAddress, User.address).call(); - if (allowance < costWei) { - let approveAction = usdToken.methods.approve(STOAddress, costWei); - await common.sendTransaction(User, approveAction, defaultGasPrice); + } else if ((raiseType != POLY) && (raiseType != ETH)) { + + let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); + let stableSymbolsAndBalance = await processAddressWithBalance(listOfStableCoins); + let stableInfo = stableSymbolsAndBalance.find(o => o.symbol === raiseType); + + if (parseInt(stableInfo.balance) >= parseInt(cost)) { + let stableCoin = common.connect(abis.erc20(), stableInfo.address); + let allowance = await stableCoin.methods.allowance(User.address, STOAddress).call(); + if (parseInt(allowance) < parseInt(costWei)) { + let approveAction = stableCoin.methods.approve(STOAddress, costWei); + await common.sendTransaction(approveAction, { from: User }); } - let actionBuyWithUSD = currentSTO.methods.buyWithUSD(User.address, costWei); - let receipt = await common.sendTransaction(User, actionBuyWithUSD, defaultGasPrice, 0, 1.5); + let actionBuyWithUSD = currentSTO.methods.buyWithUSDRateLimited(User.address, costWei, minTokenToBuy, stableInfo.address); + let receipt = await common.sendTransaction(actionBuyWithUSD, { from: User, factor: 1.5 }); logTokensPurchasedUSDTieredSTO(receipt); } else { - console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} DAI but have ${userBalance} DAI.`)); + console.log(chalk.red(`Not enough balance to Buy tokens, Require ${cost} ${stableInfo.symbol} but have ${stableInfo.balance} ${stableInfo.symbol}.`)); console.log(chalk.red(`Please purchase a smaller amount of tokens.`)); process.exit(); - } + } } else { - let actionBuyWithETH = currentSTO.methods.buyWithETH(User.address); - let receipt = await common.sendTransaction(User, actionBuyWithETH, defaultGasPrice, costWei); + let actionBuyWithETH = currentSTO.methods.buyWithETHRateLimited(User.address, minTokenToBuy); + let receipt = await common.sendTransaction(actionBuyWithETH, { from: User, value: costWei }); logTokensPurchasedUSDTieredSTO(receipt); } @@ -529,13 +651,12 @@ async function investUsdTieredSTO(currency, amount) { await showUserInfoForUSDTieredSTO(); } -async function polyBalance(_user) { - let balance = await polyToken.methods.balanceOf(_user).call(); - return web3.utils.fromWei(balance); +function selectToken(msg) { + return readlineSync.keyInSelect(raiseTypes, msg, { cancel: false }); } -async function usdBalance(_user) { - let balance = await usdToken.methods.balanceOf(_user).call(); +async function polyBalance(_user) { + let balance = await polyToken.methods.balanceOf(_user).call(); return web3.utils.fromWei(balance); } @@ -551,7 +672,7 @@ function logTokensPurchasedUSDTieredSTO(receipt) { }; } -function logTokensPurchasedCappedSTO(receipt) { +function logTokensPurchasedCappedSTO(receipt, displayRaiseType) { console.log(chalk.green(`Congratulations! The token purchase was successfully completed.`)); let events = common.getMultipleEventsFromLogs(currentSTO._jsonInterface, receipt.logs, 'TokenPurchase'); for (event of events) { @@ -564,7 +685,7 @@ function logTokensPurchasedCappedSTO(receipt) { } module.exports = { - executeApp: async function(investorAddress, investorPrivKey, symbol, currency, amount, remoteNetwork) { - return executeApp(investorAddress, investorPrivKey, symbol, currency, amount, remoteNetwork); - } + executeApp: async function (investorAddress, investorPrivKey, symbol, currency, amount) { + return executeApp(investorAddress, investorPrivKey, symbol, currency, amount); + } } diff --git a/CLI/commands/module_manager.js b/CLI/commands/module_manager.js deleted file mode 100644 index b91541a9c..000000000 --- a/CLI/commands/module_manager.js +++ /dev/null @@ -1,447 +0,0 @@ -// Libraries for terminal prompts -var readlineSync = require('readline-sync'); -var chalk = require('chalk'); -var common = require('./common/common_functions'); -var global = require('./common/global'); - -// Load contract artifacts -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis'); - -let securityTokenRegistry; -let securityToken; -let polyToken; - -// Init token info -let STSymbol; -let STAddress; -let STDetails; -let validSymbol = false; - -// Init Module Info -let pmModules; -let tmModules; -let stoModules; -let cpModules; -let numPM; -let numTM; -let numSTO; -let numCP; - -async function setup() { - try { - let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - securityTokenRegistry.setProvider(web3.currentProvider); - - let polytokenAddress = await contracts.polyToken(); - let polytokenABI = abis.polyToken(); - polyToken = new web3.eth.Contract(polytokenABI, polytokenAddress); - polyToken.setProvider(web3.currentProvider); - }catch(err){ - console.log(err); - console.log(chalk.red(`There was a problem getting the contracts. Make sure they are deployed to the selected network.`)); - process.exit(0); - } -} - -// Start function -async function executeApp(remoteNetwork) { - await global.initialize(remoteNetwork); - - common.logAsciiBull(); - console.log(chalk.yellow(`******************************************`)); - console.log(chalk.yellow(`Welcome to the Command-Line Module Manager`)); - console.log(chalk.yellow(`******************************************`)); - - await setup(); - await showUserInfo(Issuer.address); - - while (!validSymbol) { - await getSecurityToken(); - } -}; - -// get token contract based on input symbol -async function getSecurityToken() { - STSymbol = readlineSync.question(chalk.yellow(`\nEnter the symbol of the registered security token you issued: `)); - STAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(STSymbol).call(); - if (STAddress == "0x0000000000000000000000000000000000000000") { - console.log(chalk.red(`\nToken symbol provided is not a registered Security Token. Please enter another symbol.`)); - return; - } - STDetails = await securityTokenRegistry.methods.getSecurityTokenData(STAddress).call(); - if (STDetails[1] != Issuer.address) { - console.log(chalk.red(`\nYou did not issue the Security Token associated with the symbol provided. Please enter another symbol.`)); - return; - } - validSymbol = true; - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, STAddress); - - await displayModules(); - -} - -// display module status -async function displayModules() { - - await showUserInfo(Issuer.address); - - // Security Token details - let displayTokenSymbol = await securityToken.methods.symbol().call(); - let displayTokenSupply = await securityToken.methods.totalSupply().call(); - let displayUserTokens = await securityToken.methods.balanceOf(Issuer.address).call(); - - console.log(` - ************** Security Token Information *************** - - Address: ${STAddress} - - Token symbol: ${displayTokenSymbol.toUpperCase()} - - Total supply: ${web3.utils.fromWei(displayTokenSupply)} ${displayTokenSymbol.toUpperCase()} - - User balance: ${web3.utils.fromWei(displayUserTokens)} ${displayTokenSymbol.toUpperCase()} - `); - - // Module Details - pmModules = await iterateModules(1); - tmModules = await iterateModules(2); - stoModules = await iterateModules(3); - cpModules = await iterateModules(4); - - // Module Counts - numPM = pmModules.length; - numTM = tmModules.length; - numSTO = stoModules.length; - numCP = cpModules.length; - - console.log(` - ****************** Module Information ******************* - - Permission Manager: ${(numPM > 0) ? numPM : 'None'} - - Transfer Manager: ${(numTM > 0) ? numTM : 'None'} - - STO: ${(numSTO > 0) ? numSTO : 'None'} - - Checkpoint: ${(numCP > 0) ? numCP : 'None'} - `); - - if (numPM) { - console.log(chalk.green(`\n Permission Manager Modules:`)); - for (i=0;i= 75) { - distribData.push(allocData); - allocData = []; - index = 0; - } - - } else { - let userArray = new Array() - //dont need this here, as if it is NOT an address this function will fail - //let checksummedAddress = web3.utils.toChecksumAddress(data[1]); - userArray.push(data[0]) - userArray.push(data[1]); - badData.push(userArray); - fullFileData.push(userArray) - } - }) - .on("end", function () { - //Add last remainder batch - distribData.push(allocData); - allocData = []; - - mint_tokens_for_affliliates(); - }); - - stream.pipe(csvStream); - } - - async function mint_tokens_for_affliliates() { - let tokenDeployed = false; - let tokenDeployedAddress; - // Let's check if token has already been deployed, if it has, skip to STO - await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call({}, function (error, result) { - if (result != "0x0000000000000000000000000000000000000000") { - console.log('\x1b[32m%s\x1b[0m', "Token deployed at address " + result + "."); - tokenDeployedAddress = result; - tokenDeployed = true; - } - }); - if (tokenDeployed) { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, tokenDeployedAddress); - } - let modules = await securityToken.methods.getModulesByType(STO_KEY).call(); - if (modules.length > 0) { - console.log("****************************************************************************************\n"); - console.log("*************" + chalk.red(" Minting of tokens is only allowed before the STO get attached ") + "************\n"); - console.log("****************************************************************************************\n"); - process.exit(0); - } - console.log(` - ------------------------------------------------------- - ------------ Mint the tokens to affiliates ------------ - ------------------------------------------------------- - `); - - let affiliatesFailedArray = []; - let affiliatesKYCInvalidArray = []; - //this for loop will do the batches, so it should run 75, 75, 50 with 200 - for (let i = 0; i < distribData.length; i++) { - try { - let affiliatesVerifiedArray = []; - let tokensVerifiedArray = []; - //splitting the user arrays to be organized by input - for (let j = 0; j < distribData[i].length; j++) { - let investorAccount = distribData[i][j][0]; - let tokenAmount = web3.utils.toWei((distribData[i][j][1]).toString(),"ether"); - let verifiedTransaction = await securityToken.methods.verifyTransfer("0x0000000000000000000000000000000000000000", investorAccount, tokenAmount, web3.utils.fromAscii('')).call(); - if (verifiedTransaction) { - affiliatesVerifiedArray.push(investorAccount); - tokensVerifiedArray.push(tokenAmount); - } else { - let gtmModule = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); - let generalTransferManagerABI = abis.generalTransferManager(); - let generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, gtmModule[0]); - let validKYC = (await generalTransferManager.methods.whitelist(Issuer.address).call()).expiryTime > Math.floor(Date.now()/1000); - if (validKYC) { - affiliatesFailedArray.push(investorAccount); - } else { - affiliatesKYCInvalidArray.push(investorAccount); - } - } - } - let mintMultiAction = securityToken.methods.mintMulti(affiliatesVerifiedArray, tokensVerifiedArray); - let r = await common.sendTransaction(Issuer, mintMultiAction, defaultGasPrice); - console.log(`Batch ${i} - Attempting to send the Minted tokens to affiliates accounts:\n\n`, affiliatesVerifiedArray, "\n\n"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------"); - console.log("Multi Mint transaction was successful.", r.gasUsed, "gas used. Spent:", web3.utils.fromWei(BigNumber(r.gasUsed * defaultGasPrice).toString(), "ether"), "Ether"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n\n"); - - - } catch (err) { - console.log("ERROR:", err); - } - } - - console.log("Retrieving logs to determine investors have had their tokens correctly.\n\n") - - let totalInvestors = 0; - let updatedInvestors = 0; - - let investorData_Events = new Array(); - let investorObjectLookup = {}; - - let event_data = await securityToken.getPastEvents('Minted', { - fromBlock: 0, - toBlock: 'latest' - }, function (error, events) { - - }); - - for (var i = 0; i < event_data.length; i++) { - let combineArray = []; - - let investorAddress_Event = event_data[i].returnValues._to; - let amount_Event = event_data[i].returnValues._value; - let blockNumber = event_data[i].blockNumber - - combineArray.push(investorAddress_Event); - combineArray.push(amount_Event); - combineArray.push(blockNumber); - - investorData_Events.push(combineArray) - //we have already recorded it, so this is an update to our object - if (investorObjectLookup.hasOwnProperty(investorAddress_Event)) { - - //the block number form the event we are checking is bigger, so we gotta replace it - if (investorObjectLookup[investorAddress_Event].recordedBlockNumber < blockNumber) { - investorObjectLookup[investorAddress_Event] = { amount: amount_Event, recordedBlockNumber: blockNumber }; - updatedInvestors += 1; - // investorAddress_Events.push(investorAddress_Event); not needed, because we start the obj with zero events - - } else { - //do nothing. so if we find an event, and it was an older block, its old, we dont care - } - //we have never recorded this address as an object key, so we need to add it to our list of investors updated by the csv - } else { - investorObjectLookup[investorAddress_Event] = { amount: amount_Event, recordedBlockNumber: blockNumber }; - totalInvestors += 1; - // investorAddress_Events.push(investorAddress_Event); - } - } - let investorAddress_Events = Object.keys(investorObjectLookup) - - console.log(`******************** EVENT LOGS ANALYSIS COMPLETE ********************\n`); - console.log(`A total of ${totalInvestors} affiliated investors get the token\n`); - console.log(`This script in total sent ${fullFileData.length - badData.length - affiliatesFailedArray.length - affiliatesKYCInvalidArray.length} new investors and updated investors to the blockchain.\n`); - console.log(`There were ${badData.length} bad entries that didnt get sent to the blockchain in the script.\n`); - console.log(`There were ${affiliatesKYCInvalidArray.length} accounts with invalid KYC.\n`); - console.log(`There were ${affiliatesFailedArray.length} accounts that didn't get sent to the blockchain as they would fail.\n`); - - console.log("************************************************************************************************"); - console.log("OBJECT WITH EVERY USER AND THEIR MINTED TOKEN: \n\n", investorObjectLookup) - console.log("************************************************************************************************"); - console.log("LIST OF ALL INVESTORS WHO GOT THE MINTED TOKENS: \n\n", investorAddress_Events) - - let missingDistribs = []; - let failedVerificationDistribs = []; - let invalidKYCDistribs = []; - for (let l = 0; l < fullFileData.length; l++) { - if (affiliatesKYCInvalidArray.includes(fullFileData[l][0])) { - invalidKYCDistribs.push(fullFileData[l]); - } else if (affiliatesFailedArray.includes(fullFileData[l][0])) { - failedVerificationDistribs.push(fullFileData[l]); - } else if (!investorObjectLookup.hasOwnProperty(fullFileData[l][0])) { - missingDistribs.push(fullFileData[l]); - } - } - - if (invalidKYCDistribs.length > 0) { - console.log("**************************************************************************************************************************"); - console.log("The following data arrays have an invalid KYC. Please review if these accounts are whitelisted and their KYC is not expired\n"); - console.log(invalidKYCDistribs); - console.log("**************************************************************************************************************************"); - } - if (failedVerificationDistribs.length > 0) { - console.log("*********************************************************************************************************"); - console.log("-- The following data arrays failed at verifyTransfer. Please review if these accounts are whitelisted --\n"); - console.log(failedVerificationDistribs); - console.log("*********************************************************************************************************"); - } - if (missingDistribs.length > 0) { - console.log("******************************************************************************************"); - console.log("-- No Minted event was found for the following data arrays. Please review them manually --\n"); - console.log(missingDistribs); - console.log("******************************************************************************************"); - } - if (missingDistribs.length == 0 && failedVerificationDistribs.length == 0 && invalidKYCDistribs.length == 0) { - console.log("\n**************************************************************************************************************************"); - console.log("All accounts passed through from the CSV were successfully get the tokens, because we were able to read them all from events"); - console.log("****************************************************************************************************************************"); - } -} - -function isvalidToken(token) { - var tokenAmount = parseInt(token); - if((tokenAmount % 1 == 0)) { - return tokenAmount; - } - return false; -} diff --git a/CLI/commands/permission_manager.js b/CLI/commands/permission_manager.js index a2165f697..71ee21198 100644 --- a/CLI/commands/permission_manager.js +++ b/CLI/commands/permission_manager.js @@ -1,7 +1,7 @@ var readlineSync = require('readline-sync'); var chalk = require('chalk'); var common = require('./common/common_functions'); -var global = require('./common/global'); +var gbl = require('./common/global'); var contracts = require('./helpers/contract_addresses'); var abis = require('./helpers/contract_abis'); @@ -12,16 +12,7 @@ let securityToken; let generalPermissionManager; let isNewDelegate = false; -const MODULES_TYPES = { - PERMISSION: 1, - TRANSFER: 2, - STO: 3, - DIVIDEND: 4, - BURN: 5 -} - -async function executeApp(remoteNetwork) { - await global.initialize(remoteNetwork); +async function executeApp() { common.logAsciiBull(); console.log("***********************************************"); @@ -41,7 +32,7 @@ async function executeApp(remoteNetwork) { } }; -async function setup(){ +async function setup() { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); let securityTokenRegistryABI = abis.securityTokenRegistry(); @@ -49,7 +40,7 @@ async function setup(){ securityTokenRegistry.setProvider(web3.currentProvider); } catch (err) { console.log(err) - console.log('\x1b[31m%s\x1b[0m',"There was a problem getting the contracts. Make sure they are deployed to the selected network."); + console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); process.exit(0); } } @@ -65,7 +56,7 @@ async function selectST() { await selectST(); } else { let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI,result); + securityToken = new web3.eth.Contract(securityTokenABI, result); } } @@ -75,9 +66,9 @@ async function addPermissionModule() { if (result.length == 0) { console.log(chalk.red(`General Permission Manager is not attached.`)); if (readlineSync.keyInYNStrict('Do you want to add General Permission Manager Module to your Security Token?')) { - let permissionManagerFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, MODULES_TYPES.PERMISSION, 'GeneralPermissionManager'); + let permissionManagerFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.PERMISSION, 'GeneralPermissionManager'); let addModuleAction = securityToken.methods.addModule(permissionManagerFactoryAddress, web3.utils.fromAscii('', 16), 0, 0); - let receipt = await common.sendTransaction(Issuer, addModuleAction, defaultGasPrice); + let receipt = await common.sendTransaction(addModuleAction); let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); console.log(`Module deployed at address: ${event._module}`); generalPermissionManagerAddress = event._module; @@ -94,13 +85,13 @@ async function addPermissionModule() { } async function changePermissionStep() { - console.log('\n\x1b[34m%s\x1b[0m',"Permission Manager - Change Permission"); + console.log('\n\x1b[34m%s\x1b[0m', "Permission Manager - Change Permission"); let selectedDelegate = await selectDelegate(); if (isNewDelegate) { isNewDelegate = false; changePermissionAction(selectedDelegate); } else { - let selectFlow = readlineSync.keyInSelect(['Remove', 'Change permission'], 'Select an option:', {cancel: false}); + let selectFlow = readlineSync.keyInSelect(['Remove', 'Change permission'], 'Select an option:', { cancel: false }); if (selectFlow == 0) { await deleteDelegate(selectedDelegate); console.log("Delegate successfully deleted.") @@ -119,21 +110,26 @@ async function changePermissionAction(selectedDelegate) { async function deleteDelegate(address) { let deleteDelegateAction = generalPermissionManager.methods.deleteDelegate(address); - await common.sendTransaction(Issuer, deleteDelegateAction, defaultGasPrice, 0, 2); + await common.sendTransaction(deleteDelegateAction, { factor: 2 }); } // Helper functions async function selectDelegate() { let result; let delegates = await getDelegates(); - + let permissions = await getDelegatesAndPermissions(); + let options = ['Add new delegate']; - options = options.concat(delegates.map(function(d) { + + options = options.concat(delegates.map(function (d) { + let perm = renderTable(permissions, d.address); + return `Account: ${d.address} - Details: ${d.details}` + Details: ${d.details} + Permisions: ${perm}` })); - let index = readlineSync.keyInSelect(options, 'Select a delegate:', {cancel: false}); + let index = readlineSync.keyInSelect(options, 'Select a delegate:', { cancel: false }); if (index == 0) { let newDelegate = await addNewDelegate(); result = newDelegate; @@ -146,32 +142,32 @@ async function selectDelegate() { async function selectModule() { let modules = await getModulesWithPermissions(); - let options = modules.map(function(m) { + let options = modules.map(function (m) { return m.name; }); - let index = readlineSync.keyInSelect(options, 'Select a module:', {cancel: false}); + let index = readlineSync.keyInSelect(options, 'Select a module:', { cancel: false }); return modules[index]; } async function selectPermission(permissions) { - let options = permissions.map(function(p) { + let options = permissions.map(function (p) { return p }); - let index = readlineSync.keyInSelect(options, 'Select a permission:', {cancel: false}); + let index = readlineSync.keyInSelect(options, 'Select a permission:', { cancel: false }); return permissions[index]; } function isPermissionValid() { let options = ['Grant permission', 'Revoke permission']; - let index = readlineSync.keyInSelect(options, 'What do you want to do?', {cancel: false}); + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: false }); return index == 0; } async function changePermission(delegate, moduleAddress, permission, isValid) { let changePermissionAction = generalPermissionManager.methods.changePermission(delegate, moduleAddress, web3.utils.asciiToHex(permission), isValid); - let receipt = await common.sendTransaction(Issuer, changePermissionAction, defaultGasPrice, 0, 2); + let receipt = await common.sendTransaction(changePermissionAction, { factor: 2 }); common.getEventFromLogs(generalPermissionManager._jsonInterface, receipt.logs, 'ChangePermission'); - console.log(`Permission changed succesfully,`); + console.log(`Permission changed successfully!`); } async function getDelegates() { @@ -203,16 +199,16 @@ async function addNewDelegate() { limitMessage: "Must be a valid address" }); let details = readlineSync.question('Enter the delegate details (i.e `Belongs to financial firm`): ', { - limit: function(input) { + limit: function (input) { return input.length > 0; }, limitMessage: "Must be a valid string" }); let addPermissionAction = generalPermissionManager.methods.addDelegate(newDelegate, web3.utils.asciiToHex(details)); - let receipt = await common.sendTransaction(Issuer, addPermissionAction, defaultGasPrice); + let receipt = await common.sendTransaction(addPermissionAction); let event = common.getEventFromLogs(generalPermissionManager._jsonInterface, receipt.logs, 'AddDelegate'); - console.log(`Delegate added succesfully: ${event._delegate} - ${event._details}`); + console.log(`Delegate added successfully: ${event._delegate} - ${web3.utils.hexToAscii(event._details)}`); isNewDelegate = true; return event._delegate; } @@ -220,14 +216,14 @@ async function addNewDelegate() { async function getModulesWithPermissions() { let modules = []; let moduleABI = abis.moduleInterface(); - - for (const type in MODULES_TYPES) { - let modulesAttached = await securityToken.methods.getModulesByType(MODULES_TYPES[type]).call(); + + for (const type in gbl.constants.MODULES_TYPES) { + let modulesAttached = await securityToken.methods.getModulesByType(gbl.constants.MODULES_TYPES[type]).call(); for (const m of modulesAttached) { let contractTemp = new web3.eth.Contract(moduleABI, m); let permissions = await contractTemp.methods.getPermissions().call(); if (permissions.length > 0) { - modules.push({ + modules.push({ name: web3.utils.hexToAscii((await securityToken.methods.getModule(m).call())[0]), address: m, permissions: permissions.map(function (p) { return web3.utils.hexToAscii(p) }) @@ -239,8 +235,55 @@ async function getModulesWithPermissions() { return modules; } -module.exports = { - executeApp: async function(remoteNetwork) { - return executeApp(remoteNetwork); +async function getDelegatesAndPermissions() { + let moduleABI = abis.moduleInterface(); + let result = []; + for (const type in gbl.constants.MODULES_TYPES) { + let modulesAttached = await securityToken.methods.getModulesByType(gbl.constants.MODULES_TYPES[type]).call(); + for (const module of modulesAttached) { + let contractTemp = new web3.eth.Contract(moduleABI, module); + let permissions = await contractTemp.methods.getPermissions().call(); + if (permissions.length > 0) { + for (const permission of permissions) { + let allDelegates = await generalPermissionManager.methods.getAllDelegatesWithPerm(module, permission).call(); + let moduleName = web3.utils.hexToUtf8((await securityToken.methods.getModule(module).call())[0]); + let permissionName = web3.utils.hexToUtf8(permission); + for (delegateAddr of allDelegates) { + if (result[delegateAddr] == undefined) { + result[delegateAddr] = [] + } + if (result[delegateAddr][moduleName + '-' + module] == undefined) { + result[delegateAddr][moduleName + '-' + module] = [{ permission: permissionName }] + } else { + result[delegateAddr][moduleName + '-' + module].push({ permission: permissionName }) + } + } + } + } } + } + return result +} + +function renderTable(permissions, address) { + let result = ``; + if (permissions[address] != undefined) { + Object.keys(permissions[address]).forEach((module) => { + result += ` + ${module.split('-')[0]} (${module.split('-')[1]}) -> `; + (permissions[address][module]).forEach((perm) => { + result += `${perm.permission}, `; + }) + result = result.slice(0, -2); + }) + } else { + result += `-`; + } + return result +} + +module.exports = { + executeApp: async function () { + return executeApp(); + } } \ No newline at end of file diff --git a/CLI/commands/scripts/script.sh b/CLI/commands/scripts/script.sh deleted file mode 100755 index 2ee363a8f..000000000 --- a/CLI/commands/scripts/script.sh +++ /dev/null @@ -1,19 +0,0 @@ -#! /bin/bash - -if [ $1 = "Whitelist" ]; -then -echo "Running the $1 script"; -node $PWD/CLI/commands/whitelist.js $2 $3 $4 -elif [ $1 = "Multimint" ]; -then -echo "Running the $1 script"; -node $PWD/CLI/commands/multi_mint.js $2 $3 $4 -elif [ $1 = "Accredit" ]; -then -echo "Running the $1 script"; -node $PWD/CLI/commands/accredit.js $2 $3 $4 -elif [ $1 = "NonAccreditedLimit" ]; -then -echo "Running the $1 script"; -node $PWD/CLI/commands/changeNonAccreditedLimit.js $2 $3 $4 -fi diff --git a/CLI/commands/sto_manager.js b/CLI/commands/sto_manager.js new file mode 100644 index 000000000..756787e2d --- /dev/null +++ b/CLI/commands/sto_manager.js @@ -0,0 +1,1139 @@ +const readlineSync = require('readline-sync'); +const chalk = require('chalk'); +const contracts = require('./helpers/contract_addresses'); +const abis = require('./helpers/contract_abis'); +const common = require('./common/common_functions'); +const gbl = require('./common/global'); +const csvParse = require('./helpers/csv'); +const { table } = require('table'); +const STABLE = 'STABLE'; + +/////////////////// +// Constants +const ACCREDIT_DATA_CSV = `${__dirname}/../data/STO/USDTieredSTO/accredited_data.csv`; +const NON_ACCREDIT_LIMIT_DATA_CSV = `${__dirname}/../data/STO/USDTieredSTO/nonAccreditedLimits_data.csv`; + +/////////////////// +// Crowdsale params +let tokenSymbol; + +//////////////////////// +// Artifacts +let securityTokenRegistry; +let moduleRegistry; +let polyToken; +let securityToken; + +async function executeApp() { + let exit = false; + while (!exit) { + console.log('\n', chalk.blue('STO Manager - Main Menu'), '\n'); + + // Show non-archived attached STO modules + let stoModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.STO); + let nonArchivedModules = stoModules.filter(m => !m.archived); + if (nonArchivedModules.length > 0) { + console.log(`STO modules attached:`); + nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + } else { + console.log(`There are no STO modules attached`); + } + + let options = []; + if (nonArchivedModules.length > 0) { + options.push('Show existing STO information', 'Modify existing STO'); + } + options.push('Add new STO module'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); + let optionSelected = index != -1 ? options[index] : 'EXIT'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Show existing STO information': + let stoToShow = selectExistingSTO(nonArchivedModules, true); + await showSTO(stoToShow.name, stoToShow.module); + break; + case 'Modify existing STO': + let stoToModify = selectExistingSTO(nonArchivedModules); + await modifySTO(stoToModify.name, stoToModify.module); + break; + case 'Add new STO module': + await addSTOModule(); + break; + case 'EXIT': + exit = true; + break; + } + } +}; + +function selectExistingSTO(stoModules, showPaused) { + let filteredModules = stoModules; + if (!showPaused) { + filteredModules = stoModules.filter(m => !m.paused); + } + let options = filteredModules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Select a module: ', { cancel: false }); + console.log('Selected:', options[index], '\n'); + let selectedName = filteredModules[index].name; + let stoABI; + switch (selectedName) { + case 'CappedSTO': + stoABI = abis.cappedSTO(); + break; + case 'USDTieredSTO': + stoABI = abis.usdTieredSTO(); + break; + } + + let stoModule = new web3.eth.Contract(stoABI, filteredModules[index].address); + return { name: selectedName, module: stoModule }; +} + +async function showSTO(selectedSTO, currentSTO) { + switch (selectedSTO) { + case 'CappedSTO': + await cappedSTO_status(currentSTO); + break; + case 'USDTieredSTO': + await usdTieredSTO_status(currentSTO); + await showAccreditedData(currentSTO); + break; + } +} + +async function modifySTO(selectedSTO, currentSTO) { + switch (selectedSTO) { + case 'CappedSTO': + console.log(chalk.red(` + ********************************* + This option is not yet available. + *********************************`)); + break; + case 'USDTieredSTO': + await usdTieredSTO_configure(currentSTO); + break; + } +} + +async function addSTOModule(stoConfig) { + console.log(chalk.blue('Launch STO - Configuration')); + + let factorySelected; + let optionSelected; + if (typeof stoConfig === 'undefined') { + let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(gbl.constants.MODULES_TYPES.STO, securityToken.options.address).call(); + moduleList = await Promise.all(availableModules.map(async function (m) { + let moduleFactoryABI = abis.moduleFactory(); + let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); + let moduleName = web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); + let moduleVersion = await moduleFactory.methods.version().call(); + return { name: moduleName, version: moduleVersion, factoryAddress: m }; + })); + let options = moduleList.map(m => `${m.name} - ${m.version} (${m.factoryAddress})`); + + let index = readlineSync.keyInSelect(options, 'What type of STO do you want?', { cancel: 'RETURN' }); + optionSelected = index != -1 ? moduleList[index].name : 'RETURN'; + factorySelected = moduleList[index].factoryAddress; + } else { + optionSelected = stoConfig.type; + factorySelected = await await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.STO, optionSelected); + } + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'CappedSTO': + let cappedSTO = await cappedSTO_launch(stoConfig, factorySelected); + await cappedSTO_status(cappedSTO); + break; + case 'USDTieredSTO': + let usdTieredSTO = await usdTieredSTO_launch(stoConfig, factorySelected); + await usdTieredSTO_status(usdTieredSTO); + break; + } +} + +//////////////// +// Capped STO // +//////////////// +async function cappedSTO_launch(stoConfig, factoryAddress) { + console.log(chalk.blue('Launch STO - Capped STO in No. of Tokens')); + + let cappedSTOFactoryABI = abis.cappedSTOFactory(); + let cappedSTOFactory = new web3.eth.Contract(cappedSTOFactoryABI, factoryAddress); + cappedSTOFactory.setProvider(web3.currentProvider); + let stoFee = new web3.utils.BN(await cappedSTOFactory.methods.getSetupCost().call()); + + let contractBalance = new web3.utils.BN(await polyToken.methods.balanceOf(securityToken._address).call()); + if (contractBalance.lt(stoFee)) { + let transferAmount = stoFee.sub(contractBalance); + let ownerBalance = new web3.utils.BN(await polyToken.methods.balanceOf(Issuer.address).call()); + if (ownerBalance.lt(transferAmount)) { + console.log(chalk.red(`\n**************************************************************************************************************************************************`)); + console.log(chalk.red(`Not enough balance to pay the CappedSTO fee, Requires ${web3.utils.fromWei(transferAmount)} POLY but have ${web3.utils.fromWei(ownerBalance)} POLY. Access POLY faucet to get the POLY to complete this txn`)); + console.log(chalk.red(`**************************************************************************************************************************************************\n`)); + return; + } else { + let transferAction = polyToken.methods.transfer(securityToken._address, transferAmount); + let receipt = await common.sendTransaction(transferAction, { factor: 2 }); + let event = common.getEventFromLogs(polyToken._jsonInterface, receipt.logs, 'Transfer'); + console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(event._value))}`) + } + } + + let oneMinuteFromNow = new web3.utils.BN((Math.floor(Date.now() / 1000) + 60)); + let oneMonthFromNow = new web3.utils.BN((Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60))); + + let cappedSTOconfig = {}; + let useConfigFile = typeof stoConfig !== 'undefined'; + if (!useConfigFile) { + cappedSTOconfig.cap = readlineSync.question('How many tokens do you plan to sell on the STO? (500.000): '); + if (cappedSTOconfig.cap == "") cappedSTOconfig.cap = 500000; + + cappedSTOconfig.raiseType = readlineSync.question('Enter' + chalk.green(` P `) + 'for POLY raise or leave empty for Ether raise (E): '); + if (cappedSTOconfig.raiseType.toUpperCase() == 'P') { + cappedSTOconfig.raiseType = [gbl.constants.FUND_RAISE_TYPES.POLY]; + } else { + cappedSTOconfig.raiseType = [gbl.constants.FUND_RAISE_TYPES.ETH]; + } + + cappedSTOconfig.rate = readlineSync.question(`Enter the rate (1 ${cappedSTOconfig.raiseType == gbl.constants.FUND_RAISE_TYPES.POLY ? 'POLY' : 'ETH'} = X ${tokenSymbol}) for the STO (1000): `); + if (cappedSTOconfig.rate == "") cappedSTOconfig.rate = 1000; + + cappedSTOconfig.wallet = readlineSync.question('Enter the address that will receive the funds from the STO (' + Issuer.address + '): '); + if (cappedSTOconfig.wallet == "") cappedSTOconfig.wallet = Issuer.address; + + cappedSTOconfig.startTime = readlineSync.question('Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): '); + + cappedSTOconfig.endTime = readlineSync.question('Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): '); + } else { + cappedSTOconfig = stoConfig; + } + + if (cappedSTOconfig.startTime == "") cappedSTOconfig.startTime = oneMinuteFromNow; + if (cappedSTOconfig.endTime == "") cappedSTOconfig.endTime = oneMonthFromNow; + + let cappedSTOABI = abis.cappedSTO(); + let configureFunction = cappedSTOABI.find(o => o.name === 'configure' && o.type === 'function'); + let bytesSTO = web3.eth.abi.encodeFunctionCall(configureFunction, + [cappedSTOconfig.startTime, + cappedSTOconfig.endTime, + web3.utils.toWei(cappedSTOconfig.cap.toString()), + web3.utils.toWei(cappedSTOconfig.rate.toString()), + cappedSTOconfig.raiseType, + cappedSTOconfig.wallet] + ); + + let addModuleAction = securityToken.methods.addModule(cappedSTOFactory.options.address, bytesSTO, stoFee, 0); + let receipt = await common.sendTransaction(addModuleAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); + console.log(`STO deployed at address: ${event._module}`); + + let cappedSTO = new web3.eth.Contract(cappedSTOABI, event._module); + cappedSTO.setProvider(web3.currentProvider); + + return cappedSTO; +} + +async function cappedSTO_status(currentSTO) { + let displayStartTime = await currentSTO.methods.startTime().call(); + let displayEndTime = await currentSTO.methods.endTime().call(); + let displayRate = new web3.utils.BN(await currentSTO.methods.rate().call()); + let displayCap = new web3.utils.BN(await currentSTO.methods.cap().call()); + let displayWallet = await currentSTO.methods.wallet().call(); + let displayRaiseType = await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.ETH).call() ? 'ETH' : 'POLY'; + let displayFundsRaised = await currentSTO.methods.fundsRaised(gbl.constants.FUND_RAISE_TYPES[displayRaiseType]).call(); + let displayWalletBalance = web3.utils.fromWei(await getBalance(displayWallet, gbl.constants.FUND_RAISE_TYPES[displayRaiseType])); + let displayTokensSold = new web3.utils.BN(await currentSTO.methods.totalTokensSold().call()); + let displayInvestorCount = await currentSTO.methods.investorCount().call(); + let displayTokenSymbol = await securityToken.methods.symbol().call(); + + let now = Math.floor(Date.now() / 1000); + let timeTitle; + let timeRemaining; + + if (now < displayStartTime) { + timeTitle = "STO starts in: "; + timeRemaining = displayStartTime - now; + } else { + timeTitle = "Time remaining:"; + timeRemaining = displayEndTime - now; + } + + timeRemaining = common.convertToDaysRemaining(timeRemaining); + + console.log(` + *************** STO Information *************** + - Address: ${currentSTO.options.address} + - Raise Cap: ${web3.utils.fromWei(displayCap)} ${displayTokenSymbol.toUpperCase()} + - Start Time: ${new Date(displayStartTime * 1000)} + - End Time: ${new Date(displayEndTime * 1000)} + - Raise Type: ${displayRaiseType} + - Rate: 1 ${displayRaiseType} = ${web3.utils.fromWei(displayRate)} ${displayTokenSymbol.toUpperCase()} + - Wallet: ${displayWallet} + - Wallet Balance: ${displayWalletBalance} ${displayRaiseType} + ----------------------------------------------- + - ${timeTitle} ${timeRemaining} + - Funds raised: ${web3.utils.fromWei(displayFundsRaised)} ${displayRaiseType} + - Tokens sold: ${web3.utils.fromWei(displayTokensSold)} ${displayTokenSymbol.toUpperCase()} + - Tokens remaining: ${web3.utils.fromWei(displayCap.sub(displayTokensSold))} ${displayTokenSymbol.toUpperCase()} + - Investor count: ${displayInvestorCount} + `); +} + +//////////////////// +// USD Tiered STO // +//////////////////// +function fundingConfigUSDTieredSTO() { + let funding = {}; + + let selectedFunding = readlineSync.question('Enter' + chalk.green(` P `) + 'for POLY raise,' + chalk.green(` S `) + 'for Stable Coin raise,' + chalk.green(` E `) + 'for Ether raise or any combination of them (i.e.' + chalk.green(` PSE `) + 'for all): ').toUpperCase(); + + funding.raiseType = []; + if (selectedFunding.includes('E')) { + funding.raiseType.push(gbl.constants.FUND_RAISE_TYPES.ETH); + } + if (selectedFunding.includes('P')) { + funding.raiseType.push(gbl.constants.FUND_RAISE_TYPES.POLY); + } + if (selectedFunding.includes('S')) { + funding.raiseType.push(gbl.constants.FUND_RAISE_TYPES.STABLE); + } + if (funding.raiseType.length == 0) { + funding.raiseType = [gbl.constants.FUND_RAISE_TYPES.ETH, gbl.constants.FUND_RAISE_TYPES.POLY, gbl.constants.FUND_RAISE_TYPES.STABLE]; + } + + return funding; +} + +async function addressesConfigUSDTieredSTO(usdTokenRaise) { + + let addresses, menu; + + do { + + addresses = {}; + + addresses.wallet = readlineSync.question('Enter the address that will receive the funds from the STO (' + Issuer.address + '): ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: Issuer.address + }); + if (addresses.wallet == "") addresses.wallet = Issuer.address; + + addresses.reserveWallet = readlineSync.question('Enter the address that will receive remaining tokens in the case the cap is not met (' + Issuer.address + '): ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: Issuer.address + }); + if (addresses.reserveWallet == "") addresses.reserveWallet = Issuer.address; + + let listOfAddress; + + if (usdTokenRaise) { + addresses.usdToken = readlineSync.question('Enter the address (or multiple addresses separated by commas) of the USD stable coin(s): ', { + limit: function (input) { + listOfAddress = input.split(','); + return listOfAddress.every((addr) => { + return web3.utils.isAddress(addr) + }) + }, + limitMessage: "Must be a valid address", + }); + } else { + listOfAddress = [] + addresses.usdToken = []; + } + + if ((usdTokenRaise) && (!await processArray(listOfAddress))) { + console.log(chalk.yellow(`\nPlease, verify your stable coins addresses to continue with this process.\n`)) + menu = true; + } else { + menu = false; + } + + if (typeof addresses.usdToken === 'string') { + addresses.usdToken = addresses.usdToken.split(",") + } + + } while (menu); + + return addresses; +} + +async function checkSymbol(address) { + let stableCoin = common.connect(abis.erc20(), address); + try { + return await stableCoin.methods.symbol().call(); + } catch (e) { + return "" + } +} + +async function processArray(array) { + let result = true; + for (const address of array) { + let symbol = await checkSymbol(address); + if (symbol == "") { + result = false; + console.log(`${address} seems not to be a stable coin`) + } + } + return result +} + +async function processAddress(array) { + let list = []; + for (const address of array) { + let symbol = await checkSymbol(address); + list.push({ "symbol": symbol, "address": address }) + } + return list +} + +function tiersConfigUSDTieredSTO(polyRaise) { + let tiers = {}; + + let defaultTiers = 3; + tiers.tiers = parseInt(readlineSync.question(`Enter the number of tiers for the STO? (${defaultTiers}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than zero', + defaultInput: defaultTiers + })); + + let defaultTokensPerTier = [190000000, 100000000, 200000000]; + let defaultRatePerTier = ['0.05', '0.10', '0.15']; + let defaultTokensPerTierDiscountPoly = [90000000, 50000000, 100000000]; + let defaultRatePerTierDiscountPoly = ['0.025', '0.05', '0.075']; + tiers.tokensPerTier = []; + tiers.ratePerTier = []; + tiers.tokensPerTierDiscountPoly = []; + tiers.ratePerTierDiscountPoly = []; + for (let i = 0; i < tiers.tiers; i++) { + tiers.tokensPerTier[i] = readlineSync.question(`How many tokens do you plan to sell on tier No. ${i + 1}? (${defaultTokensPerTier[i]}): `, { + limit: function (input) { + return parseFloat(input) > 0; + }, + limitMessage: 'Must be greater than zero', + defaultInput: defaultTokensPerTier[i] + }); + + tiers.ratePerTier[i] = readlineSync.question(`What is the USD per token rate for tier No. ${i + 1}? (${defaultRatePerTier[i]}): `, { + limit: function (input) { + return parseFloat(input) > 0; + }, + limitMessage: 'Must be greater than zero', + defaultInput: defaultRatePerTier[i] + }); + + if (polyRaise && readlineSync.keyInYNStrict(`Do you plan to have a discounted rate for POLY investments for tier No. ${i + 1}? `)) { + tiers.tokensPerTierDiscountPoly[i] = readlineSync.question(`How many of those tokens do you plan to sell at discounted rate on tier No. ${i + 1}? (${defaultTokensPerTierDiscountPoly[i]}): `, { + limit: function (input) { + return parseFloat(input) < parseFloat(tiers.tokensPerTier[i]); + }, + limitMessage: 'Must be less than the No. of tokens of the tier', + defaultInput: defaultTokensPerTierDiscountPoly[i] + }); + + tiers.ratePerTierDiscountPoly[i] = readlineSync.question(`What is the discounted rate for tier No. ${i + 1}? (${defaultRatePerTierDiscountPoly[i]}): `, { + limit: function (input) { + return parseFloat(input) < parseFloat(tiers.ratePerTier[i]); + }, + limitMessage: 'Must be less than the rate of the tier', + defaultInput: defaultRatePerTierDiscountPoly[i] + }); + } else { + tiers.tokensPerTierDiscountPoly[i] = 0; + tiers.ratePerTierDiscountPoly[i] = 0; + } + } + + return tiers; +} + +function limitsConfigUSDTieredSTO() { + let limits = {}; + + let defaultMinimumInvestment = 5; + limits.minimumInvestmentUSD = readlineSync.question(`What is the minimum investment in USD? (${defaultMinimumInvestment}): `, { + limit: function (input) { + return parseFloat(input) > 0; + }, + limitMessage: "Must be greater than zero", + defaultInput: defaultMinimumInvestment + }); + + let nonAccreditedLimit = 2500; + limits.nonAccreditedLimitUSD = readlineSync.question(`What is the default limit for non accredited investors in USD? (${nonAccreditedLimit}): `, { + limit: function (input) { + return parseFloat(input) >= parseFloat(limits.minimumInvestmentUSD); + }, + limitMessage: "Must be greater than minimum investment", + defaultInput: nonAccreditedLimit + }); + + return limits; +} + +function timesConfigUSDTieredSTO(stoConfig) { + let times = {}; + + let oneMinuteFromNow = Math.floor(Date.now() / 1000) + 60; + if (typeof stoConfig === 'undefined') { + times.startTime = parseInt(readlineSync.question('Enter the start time for the STO (Unix Epoch time)\n(1 minutes from now = ' + oneMinuteFromNow + ' ): ', { + limit: function (input) { + return parseInt(input) > Math.floor(Date.now() / 1000); + }, + limitMessage: "Must be a future time", + defaultInput: oneMinuteFromNow + })); + } else { + times.startTime = stoConfig.times.startTime; + } + if (times.startTime == "") times.startTime = oneMinuteFromNow; + + let oneMonthFromNow = Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60); + if (typeof stoConfig === 'undefined') { + times.endTime = parseInt(readlineSync.question('Enter the end time for the STO (Unix Epoch time)\n(1 month from now = ' + oneMonthFromNow + ' ): ', { + limit: function (input) { + return parseInt(input) > times.startTime; + }, + limitMessage: "Must be greater than Start Time", + defaultInput: oneMonthFromNow + })); + } else { + times.endTime = stoConfig.times.startTime; + } + if (times.endTime == "") times.endTime = oneMonthFromNow; + + return times; +} + +async function usdTieredSTO_launch(stoConfig, factoryAddress) { + console.log(chalk.blue('Launch STO - USD pegged tiered STO')); + + let usdTieredSTOFactoryABI = abis.usdTieredSTOFactory(); + let usdTieredSTOFactory = new web3.eth.Contract(usdTieredSTOFactoryABI, factoryAddress); + usdTieredSTOFactory.setProvider(web3.currentProvider); + let stoFee = new web3.utils.BN(await usdTieredSTOFactory.methods.getSetupCost().call()); + + let contractBalance = new web3.utils.BN(await polyToken.methods.balanceOf(securityToken._address).call()); + if (contractBalance.lt(stoFee)) { + let transferAmount = stoFee.sub(contractBalance); + let ownerBalance = new web3.utils.BN(await polyToken.methods.balanceOf(Issuer.address).call()); + if (ownerBalance.lt(transferAmount)) { + console.log(chalk.red(`\n**************************************************************************************************************************************************`)); + console.log(chalk.red(`Not enough balance to pay the USDTieredSTO fee, Requires ${web3.utils.fromWei(transferAmount)} POLY but have ${web3.utils.fromWei(ownerBalance)} POLY. Access POLY faucet to get the POLY to complete this txn`)); + console.log(chalk.red(`**************************************************************************************************************************************************\n`)); + return; + } else { + let transferAction = polyToken.methods.transfer(securityToken._address, transferAmount); + let receipt = await common.sendTransaction(transferAction, { factor: 2 }); + let event = common.getEventFromLogs(polyToken._jsonInterface, receipt.logs, 'Transfer'); + console.log(`Number of POLY sent: ${web3.utils.fromWei(new web3.utils.BN(event._value))}`) + } + } + + let useConfigFile = typeof stoConfig !== 'undefined'; + let funding = useConfigFile ? stoConfig.funding : fundingConfigUSDTieredSTO(); + let addresses = useConfigFile ? stoConfig.addresses : await addressesConfigUSDTieredSTO(funding.raiseType.includes(gbl.constants.FUND_RAISE_TYPES.STABLE)); + let tiers = useConfigFile ? stoConfig.tiers : tiersConfigUSDTieredSTO(funding.raiseType.includes(gbl.constants.FUND_RAISE_TYPES.POLY)); + let limits = useConfigFile ? stoConfig.limits : limitsConfigUSDTieredSTO(); + let times = timesConfigUSDTieredSTO(stoConfig); + + let usdTieredSTOABI = abis.usdTieredSTO(); + let configureFunction = usdTieredSTOABI.find(o => o.name === 'configure' && o.type === 'function'); + let bytesSTO = web3.eth.abi.encodeFunctionCall(configureFunction, + [times.startTime, + times.endTime, + tiers.ratePerTier.map(r => web3.utils.toWei(r.toString())), + tiers.ratePerTierDiscountPoly.map(rd => web3.utils.toWei(rd.toString())), + tiers.tokensPerTier.map(t => web3.utils.toWei(t.toString())), + tiers.tokensPerTierDiscountPoly.map(td => web3.utils.toWei(td.toString())), + web3.utils.toWei(limits.nonAccreditedLimitUSD.toString()), + web3.utils.toWei(limits.minimumInvestmentUSD.toString()), + funding.raiseType, + addresses.wallet, + addresses.reserveWallet, + addresses.usdToken] + ); + + let addModuleAction = securityToken.methods.addModule(usdTieredSTOFactory.options.address, bytesSTO, stoFee, 0); + let receipt = await common.sendTransaction(addModuleAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); + console.log(`STO deployed at address: ${event._module}`); + + let usdTieredSTO = new web3.eth.Contract(usdTieredSTOABI, event._module); + usdTieredSTO.setProvider(web3.currentProvider); + + return usdTieredSTO; +} + +async function usdTieredSTO_status(currentSTO) { + let displayStartTime = await currentSTO.methods.startTime().call(); + let displayEndTime = await currentSTO.methods.endTime().call(); + let displayCurrentTier = parseInt(await currentSTO.methods.currentTier().call()) + 1; + let test = await currentSTO.methods.nonAccreditedLimitUSD().call(); + let displayNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call()); + let displayMinimumInvestmentUSD = web3.utils.fromWei(await currentSTO.methods.minimumInvestmentUSD().call()); + let displayWallet = await currentSTO.methods.wallet().call(); + let displayReserveWallet = await currentSTO.methods.reserveWallet().call(); + let displayTokensSold = web3.utils.fromWei(await currentSTO.methods.getTokensSold().call()); + let displayInvestorCount = await currentSTO.methods.investorCount().call(); + let displayIsFinalized = await currentSTO.methods.isFinalized().call() ? "YES" : "NO"; + let displayTokenSymbol = await securityToken.methods.symbol().call(); + let tiersLength = await currentSTO.methods.getNumberOfTiers().call(); + let listOfStableCoins = await currentSTO.methods.getUsdTokens().call(); + let raiseTypes = []; + let stableSymbols = []; + + for (const fundType in gbl.constants.FUND_RAISE_TYPES) { + if (await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES[fundType]).call()) { + if (fundType == STABLE) { + stableSymbols = await processAddress(listOfStableCoins) + } + raiseTypes.push(fundType); + } + } + + let displayTiers = ""; + let displayMintedPerTier = ""; + for (let t = 0; t < tiersLength; t++) { + let tier = await currentSTO.methods.tiers(t).call(); + let ratePerTier = tier.rate; + let tokensPerTierTotal = tier.tokenTotal; + let mintedPerTierTotal = tier.mintedTotal; + let mintedPerTierPerRaiseType = await currentSTO.methods.getTokensMintedByTier(t).call(); + + let displayMintedPerTierPerType = ""; + let displayDiscountTokens = ""; + for (const type of raiseTypes) { + let displayDiscountMinted = ""; + let tokensPerTierDiscountPoly = tier.tokensDiscountPoly; + if (tokensPerTierDiscountPoly > 0) { + let ratePerTierDiscountPoly = tier.rateDiscountPoly; + let mintedPerTierDiscountPoly = tier.mintedDiscountPoly; + displayDiscountTokens = ` + Tokens at discounted rate: ${web3.utils.fromWei(tokensPerTierDiscountPoly)} ${displayTokenSymbol} + Discounted rate: ${web3.utils.fromWei(ratePerTierDiscountPoly, 'ether')} USD per Token`; + + displayDiscountMinted = `(${web3.utils.fromWei(mintedPerTierDiscountPoly)} ${displayTokenSymbol} at discounted rate)`; + } + + let mintedPerTier = mintedPerTierPerRaiseType[gbl.constants.FUND_RAISE_TYPES[type]]; + if ((type == STABLE) && (stableSymbols.length)) { + displayMintedPerTierPerType += ` + Sold for stable coin(s): ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; + } else { + displayMintedPerTierPerType += ` + Sold for ${type}:\t\t ${web3.utils.fromWei(mintedPerTier)} ${displayTokenSymbol} ${displayDiscountMinted}`; + } + } + + displayTiers += ` + - Tier ${t + 1}: + Tokens: ${web3.utils.fromWei(tokensPerTierTotal)} ${displayTokenSymbol} + Rate: ${web3.utils.fromWei(ratePerTier)} USD per Token` + + displayDiscountTokens; + + displayMintedPerTier += ` + - Tokens minted in Tier ${t + 1}: ${web3.utils.fromWei(mintedPerTierTotal)} ${displayTokenSymbol}` + + displayMintedPerTierPerType; + } + + let displayFundsRaisedUSD = web3.utils.fromWei(await currentSTO.methods.fundsRaisedUSD().call()); + + let displayWalletBalancePerType = ''; + let displayReserveWalletBalancePerType = ''; + let displayFundsRaisedPerType = ''; + let displayTokensSoldPerType = ''; + for (const type of raiseTypes) { + let balance = await getBalance(displayWallet, gbl.constants.FUND_RAISE_TYPES[type]); + let walletBalance = web3.utils.fromWei(balance); + if ((type == STABLE) && (stableSymbols.length)) { + stableSymbols.forEach(async (stable) => { + let raised = await checkStableBalance(displayWallet, stable.address); + displayWalletBalancePerType += ` + Balance ${stable.symbol}:\t\t ${web3.utils.fromWei(raised)} ${stable.symbol}`; + }) + } else { + let walletBalanceUSD = web3.utils.fromWei(await currentSTO.methods.convertToUSD(gbl.constants.FUND_RAISE_TYPES[type], balance).call()); + displayWalletBalancePerType += ` + Balance ${type}:\t\t ${walletBalance} ${type} (${walletBalanceUSD} USD)`; + } + + balance = await getBalance(displayReserveWallet, gbl.constants.FUND_RAISE_TYPES[type]); + let reserveWalletBalance = web3.utils.fromWei(balance); + let reserveWalletBalanceUSD = web3.utils.fromWei(await currentSTO.methods.convertToUSD(gbl.constants.FUND_RAISE_TYPES[type], balance).call()); + if ((type == STABLE) && (stableSymbols.length)) { + stableSymbols.forEach(async (stable) => { + let raised = await checkStableBalance(displayReserveWallet, stable.address); + displayReserveWalletBalancePerType += ` + Balance ${stable.symbol}:\t\t ${web3.utils.fromWei(raised)} ${stable.symbol}`; + }) + } else { + displayReserveWalletBalancePerType += ` + Balance ${type}:\t\t ${reserveWalletBalance} ${type} (${reserveWalletBalanceUSD} USD)`; + } + + let fundsRaised = web3.utils.fromWei(await currentSTO.methods.fundsRaised(gbl.constants.FUND_RAISE_TYPES[type]).call()); + if ((type == STABLE) && (stableSymbols.length)) { + stableSymbols.forEach(async (stable) => { + let raised = await getStableCoinsRaised(currentSTO, stable.address); + displayFundsRaisedPerType += ` + ${stable.symbol}:\t\t\t ${web3.utils.fromWei(raised)} ${stable.symbol}`; + }) + } else { + displayFundsRaisedPerType += ` + ${type}:\t\t\t ${fundsRaised} ${type}`; + } + + //Only show sold for if more than one raise type are allowed + if (raiseTypes.length > 1) { + let tokensSoldPerType = web3.utils.fromWei(await currentSTO.methods.getTokensSoldFor(gbl.constants.FUND_RAISE_TYPES[type]).call()); + if ((type == STABLE) && (stableSymbols.length)) { + displayTokensSoldPerType += ` + Sold for stable coin(s): ${tokensSoldPerType} ${displayTokenSymbol}`; + } else { + displayTokensSoldPerType += ` + Sold for ${type}:\t\t ${tokensSoldPerType} ${displayTokenSymbol}`; + } + } + } + + let displayRaiseType = raiseTypes.join(' - '); + //If STO has stable coins, we list them one by one + if (stableSymbols.length) { + displayRaiseType = displayRaiseType.replace(STABLE, "") + `${stableSymbols.map((obj) => { return obj.symbol }).toString().replace(`,`, ` - `)}` + } + + let now = Math.floor(Date.now() / 1000); + let timeTitle; + let timeRemaining; + if (now < displayStartTime) { + timeTitle = "STO starts in: "; + timeRemaining = displayStartTime - now; + } else { + timeTitle = "Time remaining:"; + timeRemaining = displayEndTime - now; + } + + timeRemaining = common.convertToDaysRemaining(timeRemaining); + + console.log(` + *********************** STO Information *********************** + - Address: ${currentSTO.options.address} + - Start Time: ${new Date(displayStartTime * 1000)} + - End Time: ${new Date(displayEndTime * 1000)} + - Raise Type: ${displayRaiseType} + - Tiers: ${tiersLength}` + + displayTiers + ` + - Minimum Investment: ${displayMinimumInvestmentUSD} USD + - Non Accredited Limit: ${displayNonAccreditedLimitUSD} USD + - Wallet: ${displayWallet}` + + displayWalletBalancePerType + ` + - Reserve Wallet: ${displayReserveWallet}` + + displayReserveWalletBalancePerType + ` + + --------------------------------------------------------------- + - ${timeTitle} ${timeRemaining} + - Is Finalized: ${displayIsFinalized} + - Tokens Sold: ${displayTokensSold} ${displayTokenSymbol}` + + displayTokensSoldPerType + ` + - Current Tier: ${displayCurrentTier}` + + displayMintedPerTier + ` + - Investor count: ${displayInvestorCount} + - Funds Raised` + + displayFundsRaisedPerType + ` + Total USD: ${displayFundsRaisedUSD} USD + `); +} + +async function checkStableBalance(walletAddress, stableAddress) { + let stableCoin = common.connect(abis.erc20(), stableAddress); + try { + return await stableCoin.methods.balanceOf(walletAddress).call(); + } catch (e) { + return "" + } +} + +async function getStableCoinsRaised(currentSTO, address) { + return await currentSTO.methods.stableCoinsRaised(address).call() +} + +async function usdTieredSTO_configure(currentSTO) { + console.log(chalk.blue('STO Configuration - USD Tiered STO')); + + let isFinalized = await currentSTO.methods.isFinalized().call(); + if (isFinalized) { + console.log(chalk.red(`STO is finalized`)); + } else { + let options = []; + options.push('Finalize STO', + 'Change accredited account', 'Change accredited in batch', + 'Change non accredited limit for an account', 'Change non accredited limits in batch'); + + // If STO is not started, you can modify configuration + let now = Math.floor(Date.now() / 1000); + let startTime = await currentSTO.methods.startTime().call.call(); + if (now < startTime) { + options.push('Modify times configuration', 'Modify tiers configuration', 'Modify addresses configuration', + 'Modify limits configuration', 'Modify funding configuration'); + } + + options.push('Reclaim ETH or ERC20 token from contract'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let selected = index != -1 ? options[index] : 'Exit'; + switch (selected) { + case 'Finalize STO': + let reserveWallet = await currentSTO.methods.reserveWallet().call(); + let isVerified = await securityToken.methods.verifyTransfer('0x0000000000000000000000000000000000000000', reserveWallet, 0, web3.utils.fromAscii("")).call(); + if (isVerified) { + if (readlineSync.keyInYNStrict()) { + let finalizeAction = currentSTO.methods.finalize(); + await common.sendTransaction(finalizeAction); + } + } else { + console.log(chalk.red(`Reserve wallet (${reserveWallet}) is not able to receive remaining tokens. Check if this address is whitelisted.`)); + } + break; + case 'Change accredited account': + let investor = readlineSync.question('Enter the address to change accreditation: '); + let isAccredited = readlineSync.keyInYNStrict(`Is ${investor} accredited?`); + let investors = [investor]; + let accredited = [isAccredited]; + let changeAccreditedAction = currentSTO.methods.changeAccredited(investors, accredited); + // 2 GAS? + await common.sendTransaction(changeAccreditedAction); + break; + case 'Change accredited in batch': + await changeAccreditedInBatch(currentSTO); + break; + case 'Change non accredited limit for an account': + let account = readlineSync.question('Enter the address to change non accredited limit: '); + let limit = readlineSync.question(`Enter the limit in USD: `); + let accounts = [account]; + let limits = [web3.utils.toWei(limit)]; + let changeNonAccreditedLimitAction = currentSTO.methods.changeNonAccreditedLimit(accounts, limits); + await common.sendTransaction(changeNonAccreditedLimitAction); + break; + case 'Change non accredited limits in batch': + await changeNonAccreditedLimitsInBatch(currentSTO); + break; + case 'Modify times configuration': + await modfifyTimes(currentSTO); + await usdTieredSTO_status(currentSTO); + break; + case 'Modify tiers configuration': + await modfifyTiers(currentSTO); + await usdTieredSTO_status(currentSTO); + break; + case 'Modify addresses configuration': + await modfifyAddresses(currentSTO); + await usdTieredSTO_status(currentSTO); + break; + case 'Modify limits configuration': + await modfifyLimits(currentSTO); + await usdTieredSTO_status(currentSTO); + break; + case 'Modify funding configuration': + await modfifyFunding(currentSTO); + await usdTieredSTO_status(currentSTO); + break; + case 'Reclaim ETH or ERC20 token from contract': + await reclaimFromContract(currentSTO); + break; + } + } +} + +async function showAccreditedData(currentSTO) { + let accreditedData = await currentSTO.methods.getAccreditedData().call(); + let investorArray = accreditedData[0]; + let accreditedArray = accreditedData[1]; + let nonAccreditedLimitArray = accreditedData[2]; + + if (investorArray.length > 0) { + let dataTable = [['Investor', 'Is accredited', 'Non-accredited limit (USD)']]; + for (let i = 0; i < investorArray.length; i++) { + dataTable.push([ + investorArray[i], + accreditedArray[i] ? 'YES' : 'NO', + accreditedArray[i] ? 'N/A' : (nonAccreditedLimitArray[i] !== '0' ? web3.utils.fromWei(nonAccreditedLimitArray[i]) : 'default') + ]); + } + console.log(); + console.log(`************************************ ACCREDITED DATA *************************************`); + console.log(); + console.log(table(dataTable)); + } else { + console.log(); + console.log(chalk.yellow(`There is no accredited data to show`)); + console.log(); + } + +} + +async function changeAccreditedInBatch(currentSTO) { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ACCREDIT_DATA_CSV}): `, { + defaultInput: ACCREDIT_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => web3.utils.isAddress(row[0]) && typeof row[1] === 'boolean'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')}`)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, isAccreditedArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to change accredited accounts:\n\n`, investorArray[batch], '\n'); + let action = currentSTO.methods.changeAccredited(investorArray[batch], isAccreditedArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Change accredited 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 changeNonAccreditedLimitsInBatch(currentSTO) { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${NON_ACCREDIT_LIMIT_DATA_CSV}): `, { + defaultInput: NON_ACCREDIT_LIMIT_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])); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')}`)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, limitArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + limitArray[batch] = limitArray[batch].map(a => web3.utils.toWei(new web3.utils.BN(a))); + console.log(`Batch ${batch + 1} - Attempting to change non accredited limit to accounts:\n\n`, investorArray[batch], '\n'); + let action = currentSTO.methods.changeNonAccreditedLimit(investorArray[batch], limitArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Change non accredited limits 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 modfifyTimes(currentSTO) { + let times = timesConfigUSDTieredSTO(); + let modifyTimesAction = currentSTO.methods.modifyTimes(times.startTime, times.endTime); + await common.sendTransaction(modifyTimesAction); +} + +async function modfifyLimits(currentSTO) { + let limits = limitsConfigUSDTieredSTO(); + let modifyLimitsAction = currentSTO.methods.modifyLimits(limits.nonAccreditedLimitUSD, limits.minimumInvestmentUSD); + await common.sendTransaction(modifyLimitsAction); +} + +async function modfifyFunding(currentSTO) { + let funding = fundingConfigUSDTieredSTO(); + let modifyFundingAction = currentSTO.methods.modifyFunding(funding.raiseType); + await common.sendTransaction(modifyFundingAction); +} + +async function modfifyAddresses(currentSTO) { + let addresses = await addressesConfigUSDTieredSTO(await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.STABLE).call()); + let modifyAddressesAction = currentSTO.methods.modifyAddresses(addresses.wallet, addresses.reserveWallet, addresses.usdToken); + await common.sendTransaction(modifyAddressesAction); +} + +async function modfifyTiers(currentSTO) { + let tiers = tiersConfigUSDTieredSTO(await currentSTO.methods.fundRaiseTypes(gbl.constants.FUND_RAISE_TYPES.POLY).call()); + let modifyTiersAction = currentSTO.methods.modifyTiers( + tiers.ratePerTier, + tiers.ratePerTierDiscountPoly, + tiers.tokensPerTier, + tiers.tokensPerTierDiscountPoly, + ); + await common.sendTransaction(modifyTiersAction); +} + +async function reclaimFromContract(currentSTO) { + let options = ['ETH', 'ERC20']; + let index = readlineSync.keyInSelect(options, 'What do you want to reclaim?', { cancel: 'RETURN' }); + let selected = index != -1 ? options[index] : 'RETURN'; + switch (selected) { + case 'ETH': + let ethBalance = await this.getBalance(currentSTO.options.address, gbl.constants.FUND_RAISE_TYPES.ETH); + console.log(chalk.yellow(`Current ETH balance: ${web3.utils.fromWei(ethBalance)} ETH`)); + let reclaimETHAction = currentSTO.methods.reclaimETH(); + await common.sendTransaction(reclaimETHAction); + console.log(chalk.green('ETH has been reclaimed succesfully!')); + break; + case 'ERC20': + let erc20Address = readlineSync.question('Enter the ERC20 token address to reclaim (POLY = ' + polyToken.options.address + '): ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: polyToken.options.address + }); + let reclaimERC20Action = currentSTO.methods.reclaimERC20(erc20Address); + await common.sendTransaction(reclaimERC20Action); + console.log(chalk.green('ERC20 has been reclaimed succesfully!')); + break + } +} + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// +async function getBalance(from, type) { + switch (type) { + case gbl.constants.FUND_RAISE_TYPES.ETH: + return await web3.eth.getBalance(from); + case gbl.constants.FUND_RAISE_TYPES.POLY: + return await polyToken.methods.balanceOf(from).call(); + default: + return '0'; + } +} + +async function getAllModulesByType(type) { + function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused, _version) { + this.name = _name; + this.type = _moduleType; + this.address = _address; + this.factoryAddress = _factoryAddress; + this.archived = _archived; + this.paused = _paused; + this.version = _version; + } + + let modules = []; + + let allModules = await securityToken.methods.getModulesByType(type).call(); + + for (let i = 0; i < allModules.length; i++) { + let details = await securityToken.methods.getModule(allModules[i]).call(); + let nameTemp = web3.utils.hexToUtf8(details[0]); + let pausedTemp = null; + if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER) { + let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/${nameTemp}.json`).toString()).abi; + let contractTemp = new web3.eth.Contract(abiTemp, details[1]); + pausedTemp = await contractTemp.methods.paused().call(); + } + let factoryAbi = abis.moduleFactory(); + let factory = new web3.eth.Contract(factoryAbi, details[2]); + let versionTemp = await factory.methods.version().call(); + modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp, versionTemp)); + } + + return modules; +} + +async function initialize(_tokenSymbol) { + welcome(); + await setup(); + if (typeof _tokenSymbol === 'undefined') { + tokenSymbol = await selectToken(); + } else { + tokenSymbol = _tokenSymbol; + } + let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); + if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { + console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + process.exit(0); + } + let securityTokenABI = abis.securityToken(); + securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); + securityToken.setProvider(web3.currentProvider); +} + +function welcome() { + common.logAsciiBull(); + console.log("****************************************"); + console.log("Welcome to the Command-Line STO Manager."); + console.log("****************************************"); + console.log("The following script will allow you to manage STOs modules."); + console.log("Issuer Account: " + Issuer.address + "\n"); +} + +async function setup() { + try { + let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); + let securityTokenRegistryABI = abis.securityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + securityTokenRegistry.setProvider(web3.currentProvider); + + let moduleRegistryAddress = await contracts.moduleRegistry(); + let moduleRegistryABI = abis.moduleRegistry(); + moduleRegistry = new web3.eth.Contract(moduleRegistryABI, moduleRegistryAddress); + moduleRegistry.setProvider(web3.currentProvider); + + let polytokenAddress = await contracts.polyToken(); + let polytokenABI = abis.polyToken(); + polyToken = new web3.eth.Contract(polytokenABI, polytokenAddress); + polyToken.setProvider(web3.currentProvider); + } catch (err) { + console.log(err) + console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); + process.exit(0); + } +} + +async function selectToken() { + let result = null; + + let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); + let tokenDataArray = await Promise.all(userTokens.map(async function (t) { + let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); + return { symbol: tokenData[0], address: t }; + })); + let options = tokenDataArray.map(function (t) { + return `${t.symbol} - Deployed at ${t.address}`; + }); + options.push('Enter token symbol manually'); + + let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'EXIT' }); + let selected = index != -1 ? options[index] : 'EXIT'; + switch (selected) { + case 'Enter token symbol manually': + result = readlineSync.question('Enter the token symbol: '); + break; + case 'Exit': + process.exit(); + break; + default: + result = tokenDataArray[index].symbol; + break; + } + + return result; +} + +module.exports = { + executeApp: async function (_tokenSymbol) { + await initialize(_tokenSymbol); + return executeApp(); + }, + addSTOModule: async function (_tokenSymbol, stoConfig) { + await initialize(_tokenSymbol); + return addSTOModule(stoConfig) + } +} diff --git a/CLI/commands/strMigrator.js b/CLI/commands/strMigrator.js index 4951f680d..f27d0bfbd 100644 --- a/CLI/commands/strMigrator.js +++ b/CLI/commands/strMigrator.js @@ -4,371 +4,371 @@ var request = require('request-promise') var abis = require('./helpers/contract_abis'); var contracts = require('./helpers/contract_addresses'); var common = require('./common/common_functions'); -var global = require('./common/global'); +var gbl = require('./common/global'); let network; let minNonce; async function executeApp(toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork) { - network = remoteNetwork; - await global.initialize(remoteNetwork); - - common.logAsciiBull(); - console.log("****************************************"); - console.log("Welcome to the Command-Line STR Migrator"); - console.log("****************************************"); - console.log("The following script will migrate tokens from old STR to new one."); - console.log("Issuer Account: " + Issuer.address + "\n"); - - try { - minNonce = await common.getNonce(Issuer); - let toSecurityTokenRegistry = await step_instance_toSTR(toStrAddress); - if (typeof tokenAddress === 'undefined') { - let fromTickerRegistry = await step_instance_fromTR(fromTrAddress); - let tickers = await step_get_registered_tickers(fromTickerRegistry, singleTicker, onlyTickers); - await step_register_tickers(tickers, toSecurityTokenRegistry); - } - if (typeof onlyTickers === 'undefined') { - let fromSecurityTokenRegistry = await step_instance_fromSTR(fromStrAddress); - let tokens = await step_get_deployed_tokens(fromSecurityTokenRegistry, singleTicker); - await step_launch_STs(tokens, toSecurityTokenRegistry, tokenAddress); - } - } catch (err) { - console.log(err); - return; + network = remoteNetwork; + await global.initialize(remoteNetwork); + + common.logAsciiBull(); + console.log("****************************************"); + console.log("Welcome to the Command-Line STR Migrator"); + console.log("****************************************"); + console.log("The following script will migrate tokens from old STR to new one."); + console.log("Issuer Account: " + Issuer.address + "\n"); + + try { + minNonce = await common.getNonce(Issuer); + let toSecurityTokenRegistry = await step_instance_toSTR(toStrAddress); + if (typeof tokenAddress === 'undefined') { + let fromTickerRegistry = await step_instance_fromTR(fromTrAddress); + let tickers = await step_get_registered_tickers(fromTickerRegistry, singleTicker, onlyTickers); + await step_register_tickers(tickers, toSecurityTokenRegistry); } + if (typeof onlyTickers === 'undefined') { + let fromSecurityTokenRegistry = await step_instance_fromSTR(fromStrAddress); + let tokens = await step_get_deployed_tokens(fromSecurityTokenRegistry, singleTicker); + await step_launch_STs(tokens, toSecurityTokenRegistry, tokenAddress); + } + } catch (err) { + console.log(err); + return; + } } -async function step_instance_toSTR(toStrAddress){ - let _toStrAddress; - if (typeof toStrAddress !== 'undefined') { - if (web3.utils.isAddress(toStrAddress)) { - _toStrAddress = toStrAddress; - } else { - console.log(chalk.red("Entered toStrAddress is not a valid address.")); - return; - } +async function step_instance_toSTR(toStrAddress) { + let _toStrAddress; + if (typeof toStrAddress !== 'undefined') { + if (web3.utils.isAddress(toStrAddress)) { + _toStrAddress = toStrAddress; } else { - // _toStrAddress = readlineSync.question('Enter the new SecurityTokenRegistry address to migrate TO: ', { - // limit: function(input) { - // return web3.utils.isAddress(input); - // }, - // limitMessage: "Must be a valid address" - // }); - _toStrAddress = "0x240f9f86b1465bf1b8eb29bc88cbf65573dfdd97"; + console.log(chalk.red("Entered toStrAddress is not a valid address.")); + return; } - - console.log(`Creating SecurityTokenRegistry contract instance of address: ${_toStrAddress}...`); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - let toSTR = new web3.eth.Contract(securityTokenRegistryABI, _toStrAddress); - toSTR.setProvider(web3.currentProvider); - - return toSTR; + } else { + // _toStrAddress = readlineSync.question('Enter the new SecurityTokenRegistry address to migrate TO: ', { + // limit: function(input) { + // return web3.utils.isAddress(input); + // }, + // limitMessage: "Must be a valid address" + // }); + _toStrAddress = "0x240f9f86b1465bf1b8eb29bc88cbf65573dfdd97"; + } + + console.log(`Creating SecurityTokenRegistry contract instance of address: ${_toStrAddress}...`); + let securityTokenRegistryABI = abis.securityTokenRegistry(); + let toSTR = new web3.eth.Contract(securityTokenRegistryABI, _toStrAddress); + toSTR.setProvider(web3.currentProvider); + + return toSTR; } -async function step_instance_fromTR(fromTrAddress){ - let _fromTrAddress; - if (typeof fromTrAddress !== 'undefined') { - if (web3.utils.isAddress(fromTrAddress)) { - _fromTrAddress = fromTrAddress; - } else { - console.log(chalk.red("Entered fromTrAddress is not a valid address.")); - return; - } +async function step_instance_fromTR(fromTrAddress) { + let _fromTrAddress; + if (typeof fromTrAddress !== 'undefined') { + if (web3.utils.isAddress(fromTrAddress)) { + _fromTrAddress = fromTrAddress; } else { - // _fromTrAddress = readlineSync.question('Enter the old TikcerRegistry address to migrate FROM: ', { - // limit: function(input) { - // return web3.utils.isAddress(input); - // }, - // limitMessage: "Must be a valid address" - // }); - _fromTrAddress = "0xc31714e6759a1ee26db1d06af1ed276340cd4233"; + console.log(chalk.red("Entered fromTrAddress is not a valid address.")); + return; } - - console.log(`Creating TickerRegistry contract instance of address: ${_fromTrAddress}...`); - let tickerRegistryABI = await getABIfromEtherscan(_fromTrAddress); - let fromTR = new web3.eth.Contract(tickerRegistryABI, _fromTrAddress); - fromTR.setProvider(web3.currentProvider); - - return fromTR; + } else { + // _fromTrAddress = readlineSync.question('Enter the old TikcerRegistry address to migrate FROM: ', { + // limit: function(input) { + // return web3.utils.isAddress(input); + // }, + // limitMessage: "Must be a valid address" + // }); + _fromTrAddress = "0xc31714e6759a1ee26db1d06af1ed276340cd4233"; + } + + console.log(`Creating TickerRegistry contract instance of address: ${_fromTrAddress}...`); + let tickerRegistryABI = await getABIfromEtherscan(_fromTrAddress); + let fromTR = new web3.eth.Contract(tickerRegistryABI, _fromTrAddress); + fromTR.setProvider(web3.currentProvider); + + return fromTR; } async function step_get_registered_tickers(tickerRegistry, singleTicker, onlyTickers) { - let tickers = []; - let expiryTime = await tickerRegistry.methods.expiryLimit().call(); - - let logs = await getLogsFromEtherscan(tickerRegistry.options.address, 0, 'latest', 'LogRegisterTicker(address,string,string,bytes32,uint256)'); - if (logs.length == 0) { - console.log("No ticker registration events were emitted."); - } else { - for (let log of logs) { - let event = common.getEventFromLogs(tickerRegistry._jsonInterface, [log], 'LogRegisterTicker'); - if (typeof singleTicker === 'undefined' || event._symbol == singleTicker) { - let details = await tickerRegistry.methods.getDetails(event._symbol).call(); - let _status = details[4]; - if (typeof onlyTickers === 'undefined' || (onlyTickers && !_status)) { - let expiredTicker = details[0] == '0x0000000000000000000000000000000000000000'; - let _symbol = event._symbol; - let _owner = expiredTicker ? event._owner : details[0]; - let _name = expiredTicker ? event._name : details[2]; - let _registrationDate = expiredTicker ? event._timestamp : details[1]; - - - console.log(`------------ Ticker Registered ------------`); - console.log(`Ticker: ${_symbol}`); - console.log(`Owner: ${_owner}`); - console.log(`Token name: ${_name}`); - console.log(`Timestamp: ${_registrationDate}`); - console.log(`Transaction hash: ${log.transactionHash}`); - console.log(`-------------------------------------------`); - console.log(`\n`); - - tickers.push({ - ticker: _symbol, - owner: _owner, - name: _name, - registrationDate: new web3.utils.BN(_registrationDate), - expiryDate: new web3.utils.BN(_registrationDate).add(new web3.utils.BN(expiryTime)), - status: _status - }); - } - } + let tickers = []; + let expiryTime = await tickerRegistry.methods.expiryLimit().call(); + + let logs = await getLogsFromEtherscan(tickerRegistry.options.address, 0, 'latest', 'LogRegisterTicker(address,string,string,bytes32,uint256)'); + if (logs.length == 0) { + console.log("No ticker registration events were emitted."); + } else { + for (let log of logs) { + let event = common.getEventFromLogs(tickerRegistry._jsonInterface, [log], 'LogRegisterTicker'); + if (typeof singleTicker === 'undefined' || event._symbol == singleTicker) { + let details = await tickerRegistry.methods.getDetails(event._symbol).call(); + let _status = details[4]; + if (typeof onlyTickers === 'undefined' || (onlyTickers && !_status)) { + let expiredTicker = details[0] == '0x0000000000000000000000000000000000000000'; + let _symbol = event._symbol; + let _owner = expiredTicker ? event._owner : details[0]; + let _name = expiredTicker ? event._name : details[2]; + let _registrationDate = expiredTicker ? event._timestamp : details[1]; + + + console.log(`------------ Ticker Registered ------------`); + console.log(`Ticker: ${_symbol}`); + console.log(`Owner: ${_owner}`); + console.log(`Token name: ${_name}`); + console.log(`Timestamp: ${_registrationDate}`); + console.log(`Transaction hash: ${log.transactionHash}`); + console.log(`-------------------------------------------`); + console.log(`\n`); + + tickers.push({ + ticker: _symbol, + owner: _owner, + name: _name, + registrationDate: new web3.utils.BN(_registrationDate), + expiryDate: new web3.utils.BN(_registrationDate).add(new web3.utils.BN(expiryTime)), + status: _status + }); } + } } + } - console.log(chalk.yellow(`${tickers.length} tickers found!`)); - return tickers; + console.log(chalk.yellow(`${tickers.length} tickers found!`)); + return tickers; } async function step_register_tickers(tickers, securityTokenRegistry) { - if (tickers.length == 0) { - console.log(chalk.yellow(`There are no tickers to migrate!`)); -} else /*if (readlineSync.keyInYNStrict(`Do you want to migrate ${tickers.length} Tickers?`)) */{ - let i = 0; - let succeed = []; - let failed = []; - let totalGas = new web3.utils.BN(0); - let migrateAll = false; - for (const t of tickers) { - if (migrateAll || readlineSync.keyInYNStrict(`Do you want to migrate ${t.ticker}?`)) { - if (!migrateAll) { - migrateAll = readlineSync.keyInYNStrict(`Do you want to migrate all tickers from here?`) - } - console.log(`\n`); - console.log(`-------- Migrating ticker No ${++i}--------`); - console.log(`Ticker: ${t.ticker}`); - console.log(``); - try { - let modifyTickerAction = securityTokenRegistry.methods.modifyTicker(t.owner, t.ticker, t.name, t.registrationDate, t.expiryDate, false); - let receipt = await common.sendTransactionWithNonce(Issuer, modifyTickerAction, defaultGasPrice, minNonce); - console.log(minNonce); - minNonce = minNonce + 1; - //totalGas = totalGas.add(new web3.utils.BN(receipt.gasUsed)); - succeed.push(t); - } catch (error) { - failed.push(t); - console.log(chalk.red(`Transaction failed!!! `)) - console.log(error); - } - } + if (tickers.length == 0) { + console.log(chalk.yellow(`There are no tickers to migrate!`)); + } else /*if (readlineSync.keyInYNStrict(`Do you want to migrate ${tickers.length} Tickers?`)) */ { + let i = 0; + let succeed = []; + let failed = []; + let totalGas = new web3.utils.BN(0); + let migrateAll = false; + for (const t of tickers) { + if (migrateAll || readlineSync.keyInYNStrict(`Do you want to migrate ${t.ticker}?`)) { + if (!migrateAll) { + migrateAll = readlineSync.keyInYNStrict(`Do you want to migrate all tickers from here?`) } - - logTickerResults(succeed, failed, totalGas); + console.log(`\n`); + console.log(`-------- Migrating ticker No ${++i}--------`); + console.log(`Ticker: ${t.ticker}`); + console.log(``); + try { + let modifyTickerAction = securityTokenRegistry.methods.modifyTicker(t.owner, t.ticker, t.name, t.registrationDate, t.expiryDate, false); + let receipt = await common.sendTransaction(modifyTickerAction, { minNonce: minNonce }); + console.log(minNonce); + minNonce = minNonce + 1; + //totalGas = totalGas.add(new web3.utils.BN(receipt.gasUsed)); + succeed.push(t); + } catch (error) { + failed.push(t); + console.log(chalk.red(`Transaction failed!!! `)) + console.log(error); + } + } } + + logTickerResults(succeed, failed, totalGas); + } } -async function step_instance_fromSTR(fromStrAddress){ - let _fromStrAddress; - if (typeof fromStrAddress !== 'undefined') { - if (web3.utils.isAddress(fromStrAddress)) { - _fromStrAddress = fromStrAddress; - } else { - console.log(chalk.red("Entered fromStrAddress is not a valid address.")); - return; - } +async function step_instance_fromSTR(fromStrAddress) { + let _fromStrAddress; + if (typeof fromStrAddress !== 'undefined') { + if (web3.utils.isAddress(fromStrAddress)) { + _fromStrAddress = fromStrAddress; } else { - // _fromStrAddress = readlineSync.question('Enter the old SecurityTokenRegistry address to migrate FROM: ', { - // limit: function(input) { - // return web3.utils.isAddress(input); - // }, - // limitMessage: "Must be a valid address" - // }); - _fromStrAddress = "0xef58491224958d978facf55d2120c55a24516b98"; + console.log(chalk.red("Entered fromStrAddress is not a valid address.")); + return; } - - console.log(`Creating SecurityTokenRegistry contract instance of address: ${_fromStrAddress}...`); - let securityTokenRegistryABI = await getABIfromEtherscan(_fromStrAddress); - let fromSTR = new web3.eth.Contract(securityTokenRegistryABI, _fromStrAddress); - fromSTR.setProvider(web3.currentProvider); - - return fromSTR; + } else { + // _fromStrAddress = readlineSync.question('Enter the old SecurityTokenRegistry address to migrate FROM: ', { + // limit: function(input) { + // return web3.utils.isAddress(input); + // }, + // limitMessage: "Must be a valid address" + // }); + _fromStrAddress = "0xef58491224958d978facf55d2120c55a24516b98"; + } + + console.log(`Creating SecurityTokenRegistry contract instance of address: ${_fromStrAddress}...`); + let securityTokenRegistryABI = await getABIfromEtherscan(_fromStrAddress); + let fromSTR = new web3.eth.Contract(securityTokenRegistryABI, _fromStrAddress); + fromSTR.setProvider(web3.currentProvider); + + return fromSTR; } async function step_get_deployed_tokens(securityTokenRegistry, singleTicker) { - let tokens = []; - - //let events = await securityTokenRegistry.getPastEvents('LogNewSecurityToken', { fromBlock: 0}); - let logs = await getLogsFromEtherscan(securityTokenRegistry.options.address, 0, 'latest', 'LogNewSecurityToken(string,address,address)'); - if (logs.length == 0) { - console.log("No security token events were emitted."); - } else { - for (let log of logs) { - let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, [log], 'LogNewSecurityToken'); - if (typeof singleTicker === 'undefined' || event._ticker == singleTicker) { - let tokenAddress = event._securityTokenAddress; - let securityTokenABI = JSON.parse(require('fs').readFileSync('./CLI/data/SecurityToken1-4-0.json').toString()).abi; - console.log(`Creating SecurityToken contract instance of address: ${tokenAddress}...`); - let token = new web3.eth.Contract(securityTokenABI, tokenAddress); - token.setProvider(web3.currentProvider); - - let tokenName = await token.methods.name().call(); - let tokenSymbol = await token.methods.symbol().call(); - let tokenOwner = await token.methods.owner().call(); - let tokenDetails = await token.methods.tokenDetails().call(); - let tokenDivisible = await token.methods.granularity().call() == 1; - let tokenDeployedAt = (await getBlockfromEtherscan(web3.utils.hexToNumber(log.blockNumber))).timeStamp; - - - let gmtAddress = (await token.methods.getModule(2, 0).call())[1]; - let gtmABI = JSON.parse(require('fs').readFileSync('./CLI/data/GeneralTransferManager1-4-0.json').toString()).abi; - let gmt = new web3.eth.Contract(gtmABI, gmtAddress); - //let gtmEvents = await gmt.getPastEvents('LogModifyWhitelist', { fromBlock: event.blockNumber}); - let gtmLogs = await getLogsFromEtherscan(gmt.options.address, 0, 'latest', 'LogModifyWhitelist(address,uint256,address,uint256,uint256,uint256,bool)'); - let gtmEvents = common.getMultipleEventsFromLogs(gmt._jsonInterface, gtmLogs, 'LogModifyWhitelist'); - - let mintedEvents = []; - if (gtmEvents.length > 0) { - //mintedEvents = await token.getPastEvents('Minted', { fromBlock: event.blockNumber}); - let mintedLogs = await getLogsFromEtherscan(token.options.address, 0, 'latest', 'Minted(address,uint256)'); - mintedEvents = common.getMultipleEventsFromLogs(token._jsonInterface, mintedLogs, 'Minted'); - } - - console.log(`--------- SecurityToken launched ---------`); - console.log(`Token address: ${event._securityTokenAddress}`); - console.log(`Symbol: ${tokenSymbol}`); - console.log(`Name: ${tokenName}`); - console.log(`Owner: ${tokenOwner}`); - console.log(`Details: ${tokenDetails}`); - console.log(`Divisble: ${tokenDivisible}`); - console.log(`Deployed at: ${tokenDeployedAt}`); - console.log(`Transaction hash: ${log.transactionHash}`); - console.log(`------------------------------------------`); - console.log(``); - - - tokens.push({ - name: tokenName, - ticker: tokenSymbol, - owner: tokenOwner, - details: tokenDetails, - address: tokenAddress, - deployedAt: tokenDeployedAt, - divisble: tokenDivisible, - gmtEvents: gtmEvents, - mintedEvents: mintedEvents - }); - } + let tokens = []; + + //let events = await securityTokenRegistry.getPastEvents('LogNewSecurityToken', { fromBlock: 0}); + let logs = await getLogsFromEtherscan(securityTokenRegistry.options.address, 0, 'latest', 'LogNewSecurityToken(string,address,address)'); + if (logs.length == 0) { + console.log("No security token events were emitted."); + } else { + for (let log of logs) { + let event = common.getEventFromLogs(securityTokenRegistry._jsonInterface, [log], 'LogNewSecurityToken'); + if (typeof singleTicker === 'undefined' || event._ticker == singleTicker) { + let tokenAddress = event._securityTokenAddress; + let securityTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../data/SecurityToken1-4-0.json`).toString()).abi; + console.log(`Creating SecurityToken contract instance of address: ${tokenAddress}...`); + let token = new web3.eth.Contract(securityTokenABI, tokenAddress); + token.setProvider(web3.currentProvider); + + let tokenName = await token.methods.name().call(); + let tokenSymbol = await token.methods.symbol().call(); + let tokenOwner = await token.methods.owner().call(); + let tokenDetails = await token.methods.tokenDetails().call(); + let tokenDivisible = await token.methods.granularity().call() == 1; + let tokenDeployedAt = (await getBlockfromEtherscan(web3.utils.hexToNumber(log.blockNumber))).timeStamp; + + + let gmtAddress = (await token.methods.getModule(2, 0).call())[1]; + let gtmABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../data/GeneralTransferManager1-4-0.json`).toString()).abi; + let gmt = new web3.eth.Contract(gtmABI, gmtAddress); + //let gtmEvents = await gmt.getPastEvents('LogModifyWhitelist', { fromBlock: event.blockNumber}); + let gtmLogs = await getLogsFromEtherscan(gmt.options.address, 0, 'latest', 'LogModifyWhitelist(address,uint256,address,uint256,uint256,uint256,bool)'); + let gtmEvents = common.getMultipleEventsFromLogs(gmt._jsonInterface, gtmLogs, 'LogModifyWhitelist'); + + let mintedEvents = []; + if (gtmEvents.length > 0) { + //mintedEvents = await token.getPastEvents('Minted', { fromBlock: event.blockNumber}); + let mintedLogs = await getLogsFromEtherscan(token.options.address, 0, 'latest', 'Minted(address,uint256)'); + mintedEvents = common.getMultipleEventsFromLogs(token._jsonInterface, mintedLogs, 'Minted'); } + + console.log(`--------- SecurityToken launched ---------`); + console.log(`Token address: ${event._securityTokenAddress}`); + console.log(`Symbol: ${tokenSymbol}`); + console.log(`Name: ${tokenName}`); + console.log(`Owner: ${tokenOwner}`); + console.log(`Details: ${tokenDetails}`); + console.log(`Divisble: ${tokenDivisible}`); + console.log(`Deployed at: ${tokenDeployedAt}`); + console.log(`Transaction hash: ${log.transactionHash}`); + console.log(`------------------------------------------`); + console.log(``); + + + tokens.push({ + name: tokenName, + ticker: tokenSymbol, + owner: tokenOwner, + details: tokenDetails, + address: tokenAddress, + deployedAt: tokenDeployedAt, + divisble: tokenDivisible, + gmtEvents: gtmEvents, + mintedEvents: mintedEvents + }); + } } + } - console.log(chalk.yellow(`${tokens.length} security tokens found!`)); - return tokens; + console.log(chalk.yellow(`${tokens.length} security tokens found!`)); + return tokens; } async function step_launch_STs(tokens, securityTokenRegistry, tokenAddress) { - if (tokens.length == 0) { - console.log(chalk.yellow(`There are no security tokens to migrate!`)); -} else /*if (readlineSync.keyInYNStrict(`Do you want to migrate ${tokens.length} Security Tokens?`))*/ { - let i = 0; - let succeed = []; - let failed = []; - let totalGas = new web3.utils.BN(0); - let polymathRegistryAddress = await contracts.polymathRegistry(); - let STFactoryABI = JSON.parse(require('fs').readFileSync('./build/contracts/STFactory.json').toString()).abi; - let STFactoryAddress = await securityTokenRegistry.methods.getSTFactoryAddress().call(); - let STFactory = new web3.eth.Contract(STFactoryABI, STFactoryAddress); - for (const t of tokens) { - console.log(`\n`); - console.log(`-------- Migrating token No ${++i}--------`); - console.log(`Token symbol: ${t.ticker}`); - console.log(`Token old address: ${t.address}`); - console.log(``); - try { - // Deploying 2.0.0 Token - let newTokenAddress; - if (tokens.length == 1 && typeof tokenAddress !== 'undefined') { - if (web3.utils.isAddress(tokenAddress)) { - newTokenAddress = tokenAddress; - } else { - console.log(chalk.red('Given tokenAddress is not an address!!')); - process.exit(0); - } - } else { - let deployTokenAction = STFactory.methods.deployToken(t.name, t.ticker, 18, t.details, Issuer.address, t.divisble, polymathRegistryAddress) - let deployTokenReceipt = await common.sendTransactionWithNonce(Issuer, deployTokenAction, 25000000000, minNonce); - minNonce = minNonce + 1; - // Instancing Security Token - newTokenAddress = deployTokenReceipt.logs[deployTokenReceipt.logs.length -1].address; //Last log is the ST creation - } - console.log(chalk.green(`The migrated to 2.0.0 Security Token address is ${newTokenAddress}`)); - let newTokenABI = abis.securityToken(); - let newToken = new web3.eth.Contract(newTokenABI, newTokenAddress); - - // Checking if the old Security Token has activity - if (t.gmtEvents.length > 0) { - // Instancing GeneralTransferManager - let gmtABI = abis.generalTransferManager(); - let gmtAddress = (await newToken.methods.getModulesByName(web3.utils.toHex("GeneralTransferManager")).call())[0]; - let gmt = new web3.eth.Contract(gmtABI, gmtAddress); - // Whitelisting investors - for (const gmtEvent of t.gmtEvents) { - let modifyWhitelistAction = gmt.methods.modifyWhitelist( - gmtEvent._investor, - new web3.utils.BN(gmtEvent._fromTime), - new web3.utils.BN(gmtEvent._toTime), - new web3.utils.BN(gmtEvent._expiryTime), - gmtEvent._canBuyFromSTO - ); - let modifyWhitelistReceipt = await common.sendTransactionWithNonce(Issuer, modifyWhitelistAction, defaultGasPrice, minNonce); - minNonce = minNonce + 1; - //totalGas = totalGas.add(new web3.utils.BN(modifyWhitelistReceipt.gasUsed)); - } - // Minting tokens - for (const mintedEvent of t.mintedEvents) { - let mintAction = newToken.methods.mint(mintedEvent.to, new web3.utils.BN(mintedEvent.amount)); - let mintReceipt = await common.sendTransactionWithNonce(Issuer, mintAction, defaultGasPrice, minNonce); - minNonce = minNonce + 1; - //totalGas = totalGas.add(new web3.utils.BN(mintReceipt.gasUsed)); - } - } - - // Transferring onweship to the original owner - let transferOwnershipAction = newToken.methods.transferOwnership(t.owner); - let transferOwnershipReceipt = await common.sendTransactionWithNonce(Issuer, transferOwnershipAction, defaultGasPrice, minNonce); - minNonce = minNonce + 1; - //totalGas = totalGas.add(new web3.utils.BN(transferOwnershipReceipt.gasUsed)); - - // Adding 2.0.0 Security Token to SecurityTokenRegistry - let modifySecurityTokenAction = securityTokenRegistry.methods.modifySecurityToken(t.name, t.ticker, t.owner, newTokenAddress, t.details, t.deployedAt); - let modifySecurityTokenReceipt = await common.sendTransactionWithNonce(Issuer, modifySecurityTokenAction, defaultGasPrice, minNonce); - minNonce = minNonce + 1; - //totalGas = totalGas.add(new web3.utils.BN(modifySecurityTokenReceipt.gasUsed)); - - succeed.push(t); - console.log('done'); - } catch (error) { - failed.push(t); - console.log(chalk.red(`Transaction failed!!! `)) - console.log(error); - } + if (tokens.length == 0) { + console.log(chalk.yellow(`There are no security tokens to migrate!`)); + } else /*if (readlineSync.keyInYNStrict(`Do you want to migrate ${tokens.length} Security Tokens?`))*/ { + let i = 0; + let succeed = []; + let failed = []; + let totalGas = new web3.utils.BN(0); + let polymathRegistryAddress = await contracts.polymathRegistry(); + let STFactoryABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/STFactory.json`).toString()).abi; + let STFactoryAddress = await securityTokenRegistry.methods.getSTFactoryAddress().call(); + let STFactory = new web3.eth.Contract(STFactoryABI, STFactoryAddress); + for (const t of tokens) { + console.log(`\n`); + console.log(`-------- Migrating token No ${++i}--------`); + console.log(`Token symbol: ${t.ticker}`); + console.log(`Token old address: ${t.address}`); + console.log(``); + try { + // Deploying 2.0.0 Token + let newTokenAddress; + if (tokens.length == 1 && typeof tokenAddress !== 'undefined') { + if (web3.utils.isAddress(tokenAddress)) { + newTokenAddress = tokenAddress; + } else { + console.log(chalk.red('Given tokenAddress is not an address!!')); + process.exit(0); + } + } else { + let deployTokenAction = STFactory.methods.deployToken(t.name, t.ticker, 18, t.details, Issuer.address, t.divisble, polymathRegistryAddress) + let deployTokenReceipt = await common.sendTransaction(deployTokenAction, { minNonce: minNonce }); + minNonce = minNonce + 1; + // Instancing Security Token + newTokenAddress = deployTokenReceipt.logs[deployTokenReceipt.logs.length - 1].address; //Last log is the ST creation + } + console.log(chalk.green(`The migrated to 2.0.0 Security Token address is ${newTokenAddress}`)); + let newTokenABI = abis.securityToken(); + let newToken = new web3.eth.Contract(newTokenABI, newTokenAddress); + + // Checking if the old Security Token has activity + if (t.gmtEvents.length > 0) { + // Instancing GeneralTransferManager + let gmtABI = abis.generalTransferManager(); + let gmtAddress = (await newToken.methods.getModulesByName(web3.utils.toHex("GeneralTransferManager")).call())[0]; + let gmt = new web3.eth.Contract(gmtABI, gmtAddress); + // Whitelisting investors + for (const gmtEvent of t.gmtEvents) { + let modifyWhitelistAction = gmt.methods.modifyWhitelist( + gmtEvent._investor, + new web3.utils.BN(gmtEvent._fromTime), + new web3.utils.BN(gmtEvent._toTime), + new web3.utils.BN(gmtEvent._expiryTime), + gmtEvent._canBuyFromSTO + ); + let modifyWhitelistReceipt = await common.sendTransaction(modifyWhitelistAction, { minNonce: minNonce }); + minNonce = minNonce + 1; + //totalGas = totalGas.add(new web3.utils.BN(modifyWhitelistReceipt.gasUsed)); + } + // Minting tokens + for (const mintedEvent of t.mintedEvents) { + let mintAction = newToken.methods.mint(mintedEvent.to, new web3.utils.BN(mintedEvent.amount)); + let mintReceipt = await common.sendTransaction(mintAction, { minNonce: minNonce }); + minNonce = minNonce + 1; + //totalGas = totalGas.add(new web3.utils.BN(mintReceipt.gasUsed)); + } } - logTokenResults(succeed, failed, totalGas); + // Transferring onweship to the original owner + let transferOwnershipAction = newToken.methods.transferOwnership(t.owner); + let transferOwnershipReceipt = await common.sendTransaction(transferOwnershipAction, { minNonce: minNonce }); + minNonce = minNonce + 1; + //totalGas = totalGas.add(new web3.utils.BN(transferOwnershipReceipt.gasUsed)); + + // Adding 2.0.0 Security Token to SecurityTokenRegistry + let modifySecurityTokenAction = securityTokenRegistry.methods.modifySecurityToken(t.name, t.ticker, t.owner, newTokenAddress, t.details, t.deployedAt); + let modifySecurityTokenReceipt = await common.sendTransaction(modifySecurityTokenAction, { minNonce: minNonce }); + minNonce = minNonce + 1; + //totalGas = totalGas.add(new web3.utils.BN(modifySecurityTokenReceipt.gasUsed)); + + succeed.push(t); + console.log('done'); + } catch (error) { + failed.push(t); + console.log(chalk.red(`Transaction failed!!! `)) + console.log(error); + } } + + logTokenResults(succeed, failed, totalGas); + } } function logTokenResults(succeed, failed, totalGas) { - console.log(` + console.log(` -------------------------------------------- --------- Token Migration Results ---------- -------------------------------------------- @@ -382,7 +382,7 @@ ${failed.map(token => chalk.red(`${token.symbol} at ${token.address}`)).join('\n } function logTickerResults(succeed, failed, totalGas) { - console.log(` + console.log(` -------------------------------------------- --------- Ticker Migration Results --------- -------------------------------------------- @@ -396,61 +396,61 @@ ${failed.map(ticker => chalk.red(`${ticker.ticker}`)).join('\n')} } async function getLogsFromEtherscan(_address, _fromBlock, _toBlock, _eventSignature) { - let urlDomain = network == 'kovan' ? 'api-kovan' : 'api'; - const options = { - url: `https://${urlDomain}.etherscan.io/api`, - qs: { - module: 'logs', - action: 'getLogs', - fromBlock: _fromBlock, - toBlock: _toBlock, - address: _address, - topic0: web3.utils.sha3(_eventSignature), - apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' - }, - method: 'GET', - json: true - }; - let data = await request(options); - return data.result; + let urlDomain = network == 'kovan' ? 'api-kovan' : 'api'; + const options = { + url: `https://${urlDomain}.etherscan.io/api`, + qs: { + module: 'logs', + action: 'getLogs', + fromBlock: _fromBlock, + toBlock: _toBlock, + address: _address, + topic0: web3.utils.sha3(_eventSignature), + apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' + }, + method: 'GET', + json: true + }; + let data = await request(options); + return data.result; } async function getABIfromEtherscan(_address) { - let urlDomain = network == 'kovan' ? 'api-kovan' : 'api'; - const options = { - url: `https://${urlDomain}.etherscan.io/api`, - qs: { - module: 'contract', - action: 'getabi', - address: _address, - apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' - }, - method: 'GET', - json: true - }; - let data = await request(options); - return JSON.parse(data.result); + let urlDomain = remoteNetwork == 'kovan' ? 'api-kovan' : 'api'; + const options = { + url: `https://${urlDomain}.etherscan.io/api`, + qs: { + module: 'contract', + action: 'getabi', + address: _address, + apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' + }, + method: 'GET', + json: true + }; + let data = await request(options); + return JSON.parse(data.result); } async function getBlockfromEtherscan(_blockNumber) { - let urlDomain = network == 'kovan' ? 'api-kovan' : 'api'; - const options = { - url: `https://${urlDomain}.etherscan.io/api`, - qs: { - module: 'block', - action: 'getblockreward', - blockno: _blockNumber, - apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' - }, - method: 'GET', - json: true - }; - let data = await request(options); - return data.result; + let urlDomain = network == 'kovan' ? 'api-kovan' : 'api'; + const options = { + url: `https://${urlDomain}.etherscan.io/api`, + qs: { + module: 'block', + action: 'getblockreward', + blockno: _blockNumber, + apikey: 'THM9IUVC2DJJ6J5MTICDE6H1HGQK14X559' + }, + method: 'GET', + json: true + }; + let data = await request(options); + return data.result; } module.exports = { - executeApp: async function(toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork) { - return executeApp(toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork); - } + executeApp: async function (toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork) { + return executeApp(toStrAddress, fromTrAddress, fromStrAddress, singleTicker, tokenAddress, onlyTickers, remoteNetwork); + } }; \ No newline at end of file diff --git a/CLI/commands/token_manager.js b/CLI/commands/token_manager.js new file mode 100644 index 000000000..a908a4277 --- /dev/null +++ b/CLI/commands/token_manager.js @@ -0,0 +1,713 @@ +// Libraries for terminal prompts +const readlineSync = require('readline-sync'); +const chalk = require('chalk'); +const stoManager = require('./sto_manager'); +const transferManager = require('./transfer_manager'); +const common = require('./common/common_functions'); +const gbl = require('./common/global'); +const csvParse = require('./helpers/csv'); + +// Constants +const MULTIMINT_DATA_CSV = `${__dirname}/../data/ST/multi_mint_data.csv`; + +// Load contract artifacts +const contracts = require('./helpers/contract_addresses'); +const abis = require('./helpers/contract_abis'); + +let securityTokenRegistry; +let polyToken; +let featureRegistry; +let securityToken; + +let allModules; +let tokenSymbol + +async function setup() { + try { + let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); + let securityTokenRegistryABI = abis.securityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + securityTokenRegistry.setProvider(web3.currentProvider); + + let polytokenAddress = await contracts.polyToken(); + let polytokenABI = abis.polyToken(); + polyToken = new web3.eth.Contract(polytokenABI, polytokenAddress); + polyToken.setProvider(web3.currentProvider); + + let featureRegistryAddress = await contracts.featureRegistry(); + let featureRegistryABI = abis.featureRegistry(); + featureRegistry = new web3.eth.Contract(featureRegistryABI, featureRegistryAddress); + featureRegistry.setProvider(web3.currentProvider); + } catch (err) { + console.log(err); + console.log(chalk.red(`There was a problem getting the contracts. Make sure they are deployed to the selected network.`)); + process.exit(0); + } +} + +// Start function +async function executeApp() { + await showUserInfo(Issuer.address); + while (securityToken) { + allModules = await getAllModules(); + await displayTokenData(); + await displayModules(); + await selectAction(); + } +}; + +async function displayTokenData() { + let displayTokenSymbol = await securityToken.methods.symbol().call(); + let displayTokenDetails = await securityToken.methods.tokenDetails().call(); + let displayVersion = await securityToken.methods.getVersion().call(); + let displayTokenSupply = await securityToken.methods.totalSupply().call(); + let displayInvestorsCount = await securityToken.methods.getInvestorCount().call(); + let displayCurrentCheckpointId = await securityToken.methods.currentCheckpointId().call(); + let displayTransferFrozen = await securityToken.methods.transfersFrozen().call(); + let displayMintingFrozen = await securityToken.methods.mintingFrozen().call(); + let displayUserTokens = await securityToken.methods.balanceOf(Issuer.address).call(); + + console.log(` +*************** Security Token Information **************** +- Address: ${securityToken.options.address} +- Token symbol: ${displayTokenSymbol.toUpperCase()} +- Token details: ${displayTokenDetails} +- Token version: ${displayVersion[0]}.${displayVersion[1]}.${displayVersion[2]} +- Total supply: ${web3.utils.fromWei(displayTokenSupply)} ${displayTokenSymbol.toUpperCase()} +- Investors count: ${displayInvestorsCount} +- Current checkpoint: ${displayCurrentCheckpointId} +- Transfer frozen: ${displayTransferFrozen ? 'YES' : 'NO'} +- Minting frozen: ${displayMintingFrozen ? 'YES' : 'NO'} +- User balance: ${web3.utils.fromWei(displayUserTokens)} ${displayTokenSymbol.toUpperCase()}`); +} + +async function displayModules() { + // Module Details + let pmModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.PERMISSION); + let tmModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.TRANSFER); + let stoModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.STO); + let cpModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.DIVIDENDS); + let burnModules = allModules.filter(m => m.type == gbl.constants.MODULES_TYPES.BURN); + + // Module Counts + let numPM = pmModules.length; + let numTM = tmModules.length; + let numSTO = stoModules.length; + let numCP = cpModules.length; + let numBURN = burnModules.length; + + console.log(` +******************* Module Information ******************** +- Permission Manager: ${(numPM > 0) ? numPM : 'None'} +- Transfer Manager: ${(numTM > 0) ? numTM : 'None'} +- STO: ${(numSTO > 0) ? numSTO : 'None'} +- Checkpoint: ${(numCP > 0) ? numCP : 'None'} +- Burn: ${(numBURN > 0) ? numBURN : 'None'} + `); + + if (numPM) { + console.log(`Permission Manager Modules:`); + pmModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + } + + if (numTM) { + console.log(`Transfer Manager Modules:`); + tmModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + } + + if (numSTO) { + console.log(`STO Modules:`); + stoModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + } + + if (numCP) { + console.log(`Checkpoint Modules:`); + cpModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + } + + if (numBURN) { + console.log(` Burn Modules:`); + burnModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + } +} + +async function selectAction() { + let options = ['Update token details'/*, 'Change granularity'*/]; + + let transferFrozen = await securityToken.methods.transfersFrozen().call(); + if (transferFrozen) { + options.push('Unfreeze transfers'); + } else { + options.push('Freeze transfers'); + } + + let isMintingFrozen = await securityToken.methods.mintingFrozen().call(); + if (!isMintingFrozen) { + let isFreezeMintingAllowed = await featureRegistry.methods.getFeatureStatus('freezeMintingAllowed').call(); + if (isFreezeMintingAllowed) { + options.push('Freeze minting permanently'); + } + } + + options.push('Create a checkpoint', 'List investors') + + let currentCheckpointId = await securityToken.methods.currentCheckpointId().call(); + if (currentCheckpointId > 0) { + options.push('List investors at checkpoint') + } + + if (!isMintingFrozen) { + options.push('Mint tokens'); + } + + options.push('Manage modules', 'Withdraw tokens from contract'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Exit' }); + let selected = index == -1 ? 'Exit' : options[index]; + console.log('Selected:', selected); + switch (selected) { + case 'Update token details': + let updatedDetails = readlineSync.question('Enter new off-chain details of the token (i.e. Dropbox folder url): '); + await updateTokenDetails(updatedDetails); + break; + case 'Change granularity': + //let granularity = readlineSync.questionInt('Enter ') + //await changeGranularity(); + break; + case 'Freeze transfers': + await freezeTransfers(); + break; + case 'Unfreeze transfers': + await unfreezeTransfers(); + break; + case 'Freeze minting permanently': + await freezeMinting(); + break; + case 'Create a checkpoint': + await createCheckpoint(); + break; + case 'List investors': + await listInvestors(); + break; + case 'List investors at checkpoint': + let checkpointId = readlineSync.question('Enter the id of the checkpoint: ', { + limit: function (input) { + return parseInt(input) > 0 && parseInt(input) <= parseInt(currentCheckpointId); + }, + limitMessage: `Must be greater than 0 and less than ${currentCheckpointId}` + }); + await listInvestorsAtCheckpoint(checkpointId); + break; + case 'Mint tokens': + await mintTokens(); + break; + case 'Manage modules': + await listModuleOptions(); + break; + case 'Withdraw tokens from contract': + let tokenAddress = readlineSync.question(`Enter the ERC20 token address (POLY ${polyToken.options.address}): `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: polyToken.options.address + }); + let value = readlineSync.questionFloat('Enter the value to withdraw: ', { + limit: function (input) { + return input > 0; + }, + limitMessage: "Must be a greater than 0" + }); + await withdrawFromContract(tokenAddress, web3.utils.toWei(new web3.utils.BN(value))); + break; + case 'Exit': + process.exit(); + break; + } +} + +// Token actions +async function updateTokenDetails(updatedDetails) { + let updateTokenDetailsAction = securityToken.methods.updateTokenDetails(updatedDetails); + await common.sendTransaction(updateTokenDetailsAction); + console.log(chalk.green(`Token details have been updated successfully!`)); +} + +async function freezeTransfers() { + let freezeTransfersAction = securityToken.methods.freezeTransfers(); + await common.sendTransaction(freezeTransfersAction); + console.log(chalk.green(`Transfers have been frozen successfully!`)); +} + +async function unfreezeTransfers() { + let unfreezeTransfersAction = securityToken.methods.unfreezeTransfers(); + await common.sendTransaction(unfreezeTransfersAction); + console.log(chalk.green(`Transfers have been unfrozen successfully!`)); +} + +async function freezeMinting() { + let freezeMintingAction = securityToken.methods.freezeMinting(); + await common.sendTransaction(freezeMintingAction); + console.log(chalk.green(`Minting has been frozen successfully!.`)); +} + +async function createCheckpoint() { + let createCheckpointAction = securityToken.methods.createCheckpoint(); + let receipt = await common.sendTransaction(createCheckpointAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'CheckpointCreated'); + console.log(chalk.green(`Checkpoint ${event._checkpointId} has been created successfully!`)); +} + +async function listInvestors() { + let investors = await securityToken.methods.getInvestors().call(); + console.log(); + if (investors.length > 0) { + console.log('***** List of investors *****'); + investors.map(i => console.log(i)); + } else { + console.log(chalk.yellow('There are no investors yet')); + } +} + +async function listInvestorsAtCheckpoint(checkpointId) { + let investors = await securityToken.methods.getInvestorsAt(checkpointId).call(); + console.log(); + if (investors.length > 0) { + console.log(`*** List of investors at checkpoint ${checkpointId} ***`); + investors.map(i => console.log(i)); + } else { + console.log(chalk.yellow(`There are no investors at checkpoint ${checkpointId}`)); + } + +} + +async function mintTokens() { + let options = ['Modify whitelist', 'Mint tokens to a single address', `Mint tokens to multiple addresses from CSV`]; + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Return' }); + let selected = index == -1 ? 'Return' : options[index]; + console.log('Selected:', selected); + switch (selected) { + case 'Modify whitelist': + let investor = readlineSync.question('Enter the address to whitelist: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let fromTime = readlineSync.questionInt('Enter the time (Unix Epoch time) when the sale lockup period ends and the investor can freely sell his tokens: '); + 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: '); + let expiryTime = readlineSync.questionInt('Enter the time till investors KYC will be validated (after that investor need to do re-KYC): '); + let canBuyFromSTO = readlineSync.keyInYNStrict('Can the investor buy from security token offerings?'); + await modifyWhitelist(investor, fromTime, toTime, expiryTime, canBuyFromSTO); + break; + case 'Mint tokens to a single address': + console.log(chalk.yellow(`Investor should be previously whitelisted.`)); + let receiver = readlineSync.question(`Enter the address to receive the tokens: `); + let amount = readlineSync.question(`Enter the amount of tokens to mint: `); + await mintToSingleAddress(receiver, amount); + break; + case `Mint tokens to multiple addresses from CSV`: + console.log(chalk.yellow(`Investors should be previously whitelisted.`)); + await multiMint(); + break; + } +} + +/// Mint actions +async function modifyWhitelist(investor, fromTime, toTime, expiryTime, canBuyFromSTO) { + let gmtModules = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); + let generalTransferManagerAddress = gmtModules[0]; + let generalTransferManagerABI = abis.generalTransferManager(); + let generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, generalTransferManagerAddress); + + let modifyWhitelistAction = generalTransferManager.methods.modifyWhitelist(investor, fromTime, toTime, expiryTime, canBuyFromSTO); + let modifyWhitelistReceipt = await common.sendTransaction(modifyWhitelistAction); + let modifyWhitelistEvent = common.getEventFromLogs(generalTransferManager._jsonInterface, modifyWhitelistReceipt.logs, 'ModifyWhitelist'); + console.log(chalk.green(`${modifyWhitelistEvent._investor} has been whitelisted sucessfully!`)); +} + +async function mintToSingleAddress(_investor, _amount) { + try { + let mintAction = securityToken.methods.mint(_investor, web3.utils.toWei(_amount)); + let receipt = await common.sendTransaction(mintAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Minted'); + console.log(chalk.green(`${web3.utils.fromWei(event._value)} tokens have been minted to ${event._to} successfully.`)); + } + catch (e) { + console.log(e); + console.log(chalk.red(`Minting was not successful - Please make sure beneficiary address has been whitelisted`)); + } +} + +async function multiMint(_csvFilePath, _batchSize) { + let csvFilePath; + if (typeof _csvFilePath !== 'undefined') { + csvFilePath = _csvFilePath; + } else { + csvFilePath = readlineSync.question(`Enter the path for csv data file (${MULTIMINT_DATA_CSV}): `, { + defaultInput: MULTIMINT_DATA_CSV + }); + } + let batchSize; + if (typeof _batchSize !== 'undefined') { + batchSize = _batchSize; + } else { + 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 tokenDivisible = await securityToken.methods.granularity().call() == 1; + let validData = parsedData.filter(row => + web3.utils.isAddress(row[0]) && + (!isNaN(row[1]) && (tokenDivisible || parseFloat(row[1]) % 1 == 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 verifiedData = []; + let unverifiedData = []; + for (const row of validData) { + let investorAccount = row[0]; + let tokenAmount = web3.utils.toWei(row[1].toString()); + let verifiedTransaction = await securityToken.methods.verifyTransfer(gbl.constants.ADDRESS_ZERO, investorAccount, tokenAmount, web3.utils.fromAscii('')).call(); + if (verifiedTransaction) { + verifiedData.push(row); + } else { + unverifiedData.push(row); + } + } + + let batches = common.splitIntoBatches(verifiedData, batchSize); + let [investorArray, amountArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to mint tokens to accounts: \n\n`, investorArray[batch], '\n'); + amountArray[batch] = amountArray[batch].map(a => web3.utils.toWei(a.toString())); + let action = securityToken.methods.mintMulti(investorArray[batch], amountArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Multi mint 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`); + } + + if (unverifiedData.length > 0) { + console.log("*********************************************************************************************************"); + console.log('The following data arrays failed at verifyTransfer. Please review if these accounts are whitelisted\n'); + console.log(chalk.red(unverifiedData.map(d => `${d[0]}, ${d[1]}`).join('\n'))); + console.log("*********************************************************************************************************"); + } +} + +async function withdrawFromContract(erc20address, value) { + let withdrawAction = securityToken.methods.withdrawERC20(erc20address, value); + await common.sendTransaction(withdrawAction); + console.log(chalk.green(`Withdrawn has been successful!.`)); +} +/// + +async function listModuleOptions() { + let options = ['Add a module'] + + let unpausedModules = allModules.filter(m => m.paused == false); + if (unpausedModules.length > 0) { + options.push('Pause a module'); + } + + let pausedModules = allModules.filter(m => m.paused == true); + if (pausedModules.length > 0) { + options.push('Unpause a module'); + } + + let unarchivedModules = allModules.filter(m => !m.archived); + if (unarchivedModules.length > 0) { + options.push('Archive a module'); + } + + let archivedModules = allModules.filter(m => m.archived); + if (archivedModules.length > 0) { + options.push('Unarchive a module', 'Remove a module'); + } + + if (allModules.length > 0) { + options.push('Change module budget'); + } + + let index = readlineSync.keyInSelect(options, chalk.yellow('What do you want to do?'), { cancel: 'Return' }); + let selected = index == -1 ? 'Exit' : options[index]; + console.log('Selected:', selected); + switch (selected) { + case 'Add a module': + await addModule(); + break; + case 'Pause a module': + await pauseModule(unpausedModules); + break; + case 'Unpause a module': + await unpauseModule(pausedModules); + break; + case 'Archive a module': + await archiveModule(unarchivedModules); + break; + case 'Unarchive a module': + await unarchiveModule(archivedModules); + break; + case 'Remove a module': + await removeModule(archivedModules); + break; + case 'Change module budget': + await changeBudget(allModules); + break; + } +} + +// Modules a actions +async function addModule() { + let options = ['Permission Manager', 'Transfer Manager', 'Security Token Offering', 'Dividends', 'Burn']; + let index = readlineSync.keyInSelect(options, 'What type of module would you like to add?', { cancel: 'Return' }); + switch (options[index]) { + case 'Permission Manager': + console.log(chalk.red(` + ********************************* + This option is not yet available. + *********************************`)); + break; + case 'Transfer Manager': + await transferManager.addTransferManagerModule(tokenSymbol) + break; + case 'Security Token Offering': + await stoManager.addSTOModule(tokenSymbol) + break; + case 'Dividends': + console.log(chalk.red(` + ********************************* + This option is not yet available. + *********************************`)); + break; + case 'Burn': + console.log(chalk.red(` + ********************************* + This option is not yet available. + *********************************`)); + break; + } +} + +async function pauseModule(modules) { + let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module would you like to pause?'); + if (index != -1) { + console.log("\nSelected:", options[index]); + let moduleABI; + if (modules[index].type == gbl.constants.MODULES_TYPES.STO) { + moduleABI = abis.sto(); + } else if (modules[index].type == gbl.constants.MODULES_TYPES.TRANSFER) { + moduleABI = abis.ITransferManager(); + } else if (modules[index].type == gbl.constants.MODULES_TYPES.DIVIDENDS) { + moduleABI = abis.erc20DividendCheckpoint(); + } else { + console.log(chalk.red(`Only STO, TM and DIVIDEND modules can be paused/unpaused`)); + process.exit(0); + } + let pausableModule = new web3.eth.Contract(moduleABI, modules[index].address); + let pauseAction = pausableModule.methods.pause(); + await common.sendTransaction(pauseAction); + console.log(chalk.green(`${modules[index].name} has been paused successfully!`)); + } +} + +async function unpauseModule(modules) { + let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module would you like to pause?'); + if (index != -1) { + console.log("\nSelected: ", options[index]); + let moduleABI; + if (modules[index].type == gbl.constants.MODULES_TYPES.STO) { + moduleABI = abis.sto(); + } else if (modules[index].type == gbl.constants.MODULES_TYPES.TRANSFER) { + moduleABI = abis.ITransferManager(); + } else if (modules[index].type == gbl.constants.MODULES_TYPES.DIVIDENDS) { + moduleABI = abis.erc20DividendCheckpoint(); + } else { + console.log(chalk.red(`Only STO, TM and DIVIDEND modules can be paused/unpaused`)); + process.exit(0); + } + let pausableModule = new web3.eth.Contract(moduleABI, modules[index].address); + let unpauseAction = pausableModule.methods.unpause(); + await common.sendTransaction(unpauseAction); + console.log(chalk.green(`${modules[index].name} has been unpaused successfully!`)); + } +} + +async function archiveModule(modules) { + let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module would you like to archive?'); + if (index != -1) { + console.log("\nSelected: ", options[index]); + let archiveModuleAction = securityToken.methods.archiveModule(modules[index].address); + await common.sendTransaction(archiveModuleAction, { factor: 2 }); + console.log(chalk.green(`${modules[index].name} has been archived successfully!`)); + } +} + +async function unarchiveModule(modules) { + let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module would you like to unarchive?'); + if (index != -1) { + console.log("\nSelected: ", options[index]); + let unarchiveModuleAction = securityToken.methods.unarchiveModule(modules[index].address); + await common.sendTransaction(unarchiveModuleAction, { factor: 2 }); + console.log(chalk.green(`${modules[index].name} has been unarchived successfully!`)); + } +} + +async function removeModule(modules) { + let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module would you like to remove?'); + if (index != -1) { + console.log("\nSelected: ", options[index]); + let removeModuleAction = securityToken.methods.removeModule(modules[index].address); + await common.sendTransaction(removeModuleAction, { factor: 2 }); + console.log(chalk.green(`${modules[index].name} has been removed successfully!`)); + } +} + +async function changeBudget(modules) { + let options = modules.map(m => `${m.name} (${m.version}) at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module would you like to change budget for?'); + if (index != -1) { + console.log("\nSelected: ", options[index]); + let increase = 0 == readlineSync.keyInSelect(['Increase', 'Decrease'], `Do you want to increase or decrease budget?`, { cancel: false }); + let amount = readlineSync.question(`Enter the amount of POLY to change in allowance`); + let changeModuleBudgetAction = securityToken.methods.changeModuleBudget(modules[index].address, web3.utils.toWei(amount), increase); + await common.sendTransaction(changeModuleBudgetAction); + console.log(chalk.green(`Module budget has been changed successfully!`)); + } +} + +// Helpers +async function showUserInfo(_user) { + console.log(` +******************** User Information ********************* +- Address: ${_user} +- POLY balance: ${web3.utils.fromWei(await polyToken.methods.balanceOf(_user).call())} +- ETH balance: ${web3.utils.fromWei(await web3.eth.getBalance(_user))} + `); +} + +async function getAllModules() { + function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused, _version) { + this.name = _name; + this.type = _moduleType; + this.address = _address; + this.factoryAddress = _factoryAddress; + this.archived = _archived; + this.paused = _paused; + this.version = _version; + } + + let modules = []; + + // Iterate over all module types + for (let type = 1; type <= 5; type++) { + let allModules = await securityToken.methods.getModulesByType(type).call(); + + // Iterate over all modules of each type + for (let i = 0; i < allModules.length; i++) { + try { + let details = await securityToken.methods.getModule(allModules[i]).call(); + let nameTemp = web3.utils.hexToUtf8(details[0]); + let pausedTemp = null; + let factoryAbi = abis.moduleFactory(); + let factory = new web3.eth.Contract(factoryAbi, details[2]); + let versionTemp = await factory.methods.version().call(); + if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER || (type == gbl.constants.MODULES_TYPES.DIVIDENDS && versionTemp === '2.1.1')) { + let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/${nameTemp}.json`).toString()).abi; + let contractTemp = new web3.eth.Contract(abiTemp, details[1]); + pausedTemp = await contractTemp.methods.paused().call(); + } + + modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp, versionTemp)); + } catch (error) { + console.log(error); + console.log(chalk.red(` + ************************* + Unable to iterate over module type - unexpected error + *************************`)); + } + } + } + + return modules; +} + +async function initialize(_tokenSymbol) { + welcome(); + await setup(); + if (typeof _tokenSymbol === 'undefined') { + tokenSymbol = await selectToken(); + } else { + tokenSymbol = _tokenSymbol; + } + let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); + if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { + console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + process.exit(0); + } + let securityTokenABI = abis.securityToken(); + securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); + securityToken.setProvider(web3.currentProvider); +} + +function welcome() { + common.logAsciiBull(); + console.log(`*****************************************`); + console.log(`Welcome to the Command-Line Token Manager`); + console.log(`*****************************************`); + console.log("The following script will allow you to manage your ST-20 tokens"); + console.log("Issuer Account: " + Issuer.address + "\n"); +} + +async function selectToken() { + let result = null; + + let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); + let tokenDataArray = await Promise.all(userTokens.map(async function (t) { + let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); + return { symbol: tokenData[0], address: t }; + })); + let options = tokenDataArray.map(function (t) { + return `${t.symbol} - Deployed at ${t.address}`; + }); + options.push('Enter token symbol manually'); + + let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' }); + let selected = index != -1 ? options[index] : 'Exit'; + switch (selected) { + case 'Enter token symbol manually': + result = readlineSync.question('Enter the token symbol: '); + break; + case 'Exit': + process.exit(); + break; + default: + result = tokenDataArray[index].symbol; + break; + } + + return result; +} + +module.exports = { + executeApp: async function (_tokenSymbol) { + await initialize(_tokenSymbol); + return executeApp(); + }, + multiMint: async function (_tokenSymbol, _csvPath, _batchSize) { + await initialize(_tokenSymbol); + return multiMint(_csvPath, _batchSize); + } +} diff --git a/CLI/commands/transfer.js b/CLI/commands/transfer.js index 48fbb79e5..5bd6d1859 100644 --- a/CLI/commands/transfer.js +++ b/CLI/commands/transfer.js @@ -1,5 +1,4 @@ var common = require('./common/common_functions'); -var global = require('./common/global'); /////////////////////////////ARTIFACTS////////////////////////////////////////// var contracts = require('./helpers/contract_addresses'); @@ -14,13 +13,11 @@ let securityToken; //////////////////////////////////////////ENTRY INTO SCRIPT////////////////////////////////////////// -async function startScript(tokenSymbol, transferTo, transferAmount, remoteNetwork) { +async function startScript(tokenSymbol, transferTo, transferAmount) { _tokenSymbol = tokenSymbol; _transferTo = transferTo; _transferAmount = transferAmount; - await global.initialize(remoteNetwork); - try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); let securityTokenRegistryABI = abis.securityTokenRegistry(); @@ -46,7 +43,7 @@ async function transfer() { try{ let transferAction = securityToken.methods.transfer(_transferTo,web3.utils.toWei(_transferAmount,"ether")); - let receipt = await common.sendTransaction(Issuer, transferAction, defaultGasPrice); + let receipt = await common.sendTransaction(transferAction); let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); console.log(` Account ${event.from} @@ -61,7 +58,7 @@ async function transfer() { }; module.exports = { - executeApp: async function(tokenSymbol, transferTo, transferAmount, remoteNetwork) { - return startScript(tokenSymbol, transferTo, transferAmount, remoteNetwork); + executeApp: async function(tokenSymbol, transferTo, transferAmount) { + return startScript(tokenSymbol, transferTo, transferAmount); } } diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index df06c68f4..13f59b128 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -1,144 +1,2800 @@ -var readlineSync = require('readline-sync'); -var chalk = require('chalk'); -var moment = require('moment'); -var common = require('./common/common_functions'); -var global = require('./common/global'); -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis'); +const readlineSync = require('readline-sync'); +const chalk = require('chalk'); +const moment = require('moment'); +const common = require('./common/common_functions'); +const contracts = require('./helpers/contract_addresses'); +const abis = require('./helpers/contract_abis'); +const gbl = require('./common/global'); +const csvParse = require('./helpers/csv'); +const { table } = require('table'); + +/////////////////// +// Constants +const WHITELIST_DATA_CSV = `${__dirname}/../data/Transfer/GTM/whitelist_data.csv`; +const ADD_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/add_blacklist_data.csv`; +const MODIFY_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/modify_blacklist_data.csv`; +const DELETE_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/delete_blacklist_data.csv`; +const ADD_INVESTOR_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/add_investor_blacklist_data.csv`; +const REMOVE_INVESTOR_BLACKLIST_DATA_CSV = `${__dirname}/../data/Transfer/BlacklistTM/remove_investor_blacklist_data.csv`; +const PERCENTAGE_WHITELIST_DATA_CSV = `${__dirname}/../data/Transfer/PercentageTM/whitelist_data.csv`; +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_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/add_custom_restriction_data.csv`; +const MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/modify_custom_restriction_data.csv`; +const REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV = `${__dirname}/../data/Transfer/VRTM/remove_custom_restriction_data.csv`; +const ADD_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/add_lockup_data.csv`; +const MODIFY_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/modify_lockup_data.csv`; +const DELETE_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/delete_lockup_data.csv`; +const ADD_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/add_lockup_investor_data.csv`; +const REMOVE_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/remove_lockup_investor_data.csv`; + +const RESTRICTION_TYPES = ['Fixed', 'Percentage']; + +const MATM_MENU_ADD = 'Add new manual approval'; +const MATM_MENU_MANAGE = 'Manage existing approvals'; +const MATM_MENU_EXPLORE = 'Explore account'; +const MATM_MENU_OPERATE = 'Operate with multiple approvals'; +const MATM_MENU_MANAGE_INCRESE = 'Increase allowance'; +const MATM_MENU_MANAGE_DECREASE = 'Decrease allowance'; +const MATM_MENU_MANAGE_TIME = 'Modify expiry time and/or description'; +const MATM_MENU_MANAGE_REVOKE = 'Revoke this approval'; +const MATM_MENU_OPERATE_ADD = 'Add multiple approvals in batch'; +const MATM_MENU_OPERATE_MODIFY = 'Modify multiple approvals in batch'; +const MATM_MENU_OPERATE_REVOKE = 'Revoke multiple approvals in batch'; // App flow let tokenSymbol; let securityToken; let securityTokenRegistry; +let moduleRegistry; +let currentTransferManager; + +async function executeApp() { + console.log('\n', chalk.blue('Transfer Manager - Main Menu', '\n')); + + let tmModules = await getAllModulesByType(gbl.constants.MODULES_TYPES.TRANSFER); + let nonArchivedModules = tmModules.filter(m => !m.archived); + if (nonArchivedModules.length > 0) { + console.log(`Transfer Manager modules attached:`); + nonArchivedModules.map(m => console.log(`- ${m.name} at ${m.address}`)) + } else { + console.log(`There are no Transfer Manager modules attached`); + } + + let options = ['Verify transfer', 'Transfer']; + let forcedTransferDisabled = await securityToken.methods.controllerDisabled().call(); + if (!forcedTransferDisabled) { + options.push('Forced transfers'); + } + if (nonArchivedModules.length > 0) { + options.push('Config existing modules'); + } + options.push('Add new Transfer Manager module'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' }); + let optionSelected = index != -1 ? options[index] : 'EXIT'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Verify transfer': + let verifyTotalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + await logTotalInvestors(); + let verifyTransferFrom = readlineSync.question(`Enter the sender account (${Issuer.address}): `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: Issuer.address + }); + await logBalance(verifyTransferFrom, verifyTotalSupply); + let verifyTransferTo = readlineSync.question('Enter the receiver account: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + await logBalance(verifyTransferTo, verifyTotalSupply); + let verifyTransferAmount = readlineSync.question('Enter amount of tokens to verify: '); + let isVerified = await securityToken.methods.verifyTransfer(verifyTransferFrom, verifyTransferTo, web3.utils.toWei(verifyTransferAmount), web3.utils.fromAscii("")).call(); + if (isVerified) { + console.log(chalk.green(`\n${verifyTransferAmount} ${tokenSymbol} can be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + } else { + console.log(chalk.red(`\n${verifyTransferAmount} ${tokenSymbol} can't be transferred from ${verifyTransferFrom} to ${verifyTransferTo}!`)); + } + break; + case 'Transfer': + let totalSupply = web3.utils.fromWei(await securityToken.methods.totalSupply().call()); + await logTotalInvestors(); + await logBalance(Issuer.address, totalSupply); + let transferTo = readlineSync.question('Enter beneficiary of tranfer: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + await logBalance(transferTo, totalSupply); + let transferAmount = readlineSync.question('Enter amount of tokens to transfer: '); + let isTranferVerified = await securityToken.methods.verifyTransfer(Issuer.address, transferTo, web3.utils.toWei(transferAmount), web3.utils.fromAscii("")).call(); + if (isTranferVerified) { + let transferAction = securityToken.methods.transfer(transferTo, web3.utils.toWei(transferAmount)); + let receipt = await common.sendTransaction(transferAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'Transfer'); + console.log(chalk.green(`${event.from} transferred ${web3.utils.fromWei(event.value)} ${tokenSymbol} to ${event.to} successfully!`)); + await logTotalInvestors(); + await logBalance(Issuer.address, totalSupply); + await logBalance(transferTo, totalSupply); + } else { + console.log(chalk.red(`Transfer failed at verification. Please review the transfer restrictions.`)); + } + break; + case 'Forced transfers': + await forcedTransfers(); + break; + case 'Config existing modules': + await configExistingModules(nonArchivedModules); + break; + case 'Add new Transfer Manager module': + await addTransferManagerModule(); + break; + case 'EXIT': + return; + } + + await executeApp(); +} + +async function forcedTransfers() { + let options = ['Disable controller', 'Set controller']; + let controller = await securityToken.methods.controller().call(); + if (controller == Issuer.address) { + options.push('Force Transfer'); + } + 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 'Disable controller': + if (readlineSync.keyInYNStrict()) { + let disableControllerAction = securityToken.methods.disableController(); + await common.sendTransaction(disableControllerAction); + console.log(chalk.green(`Forced transfers have been disabled permanently`)); + } + break; + case 'Set controller': + let controllerAddress = readlineSync.question(`Enter the address for the controller (${Issuer.address}): `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: Issuer.address + }); + let setControllerAction = securityToken.methods.setController(controllerAddress); + let setControllerReceipt = await common.sendTransaction(setControllerAction); + let setControllerEvent = common.getEventFromLogs(securityToken._jsonInterface, setControllerReceipt.logs, 'SetController'); + console.log(chalk.green(`New controller is ${setControllerEvent._newController}`)); + break; + case 'Force Transfer': + let from = readlineSync.question('Enter the address from which to take tokens: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(from).call()); + console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol}`)); + let to = readlineSync.question('Enter address where to send tokens: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + }); + let toBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(to).call()); + console.log(chalk.yellow(`Balance of ${to}: ${toBalance} ${tokenSymbol}`)); + let amount = readlineSync.question('Enter amount of tokens to transfer: ', { + limit: function (input) { + return parseInt(input) <= parseInt(fromBalance); + }, + limitMessage: `Amount must be less or equal than ${fromBalance} ${tokenSymbol}`, + }); + let data = '';//readlineSync.question('Enter the data to indicate validation: '); + let log = readlineSync.question('Enter a message to attach to the transfer (i.e. "Private key lost"): '); + let forceTransferAction = securityToken.methods.forceTransfer(from, to, web3.utils.toWei(amount), web3.utils.asciiToHex(data), web3.utils.asciiToHex(log)); + let forceTransferReceipt = await common.sendTransaction(forceTransferAction, { factor: 1.5 }); + let forceTransferEvent = common.getEventFromLogs(securityToken._jsonInterface, forceTransferReceipt.logs, 'ForceTransfer'); + console.log(chalk.green(` ${forceTransferEvent._controller} has successfully forced a transfer of ${web3.utils.fromWei(forceTransferEvent._value)} ${tokenSymbol} + from ${forceTransferEvent._from} to ${forceTransferEvent._to} + Verified transfer: ${forceTransferEvent._verifyTransfer} + Data: ${web3.utils.hexToAscii(forceTransferEvent._data)} + `)); + console.log(`Balance of ${from} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(from).call())} ${tokenSymbol}`); + console.log(`Balance of ${to} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(to).call())} ${tokenSymbol}`); + break; + case 'RETURN': + return; + } + + await forcedTransfers(); +} + +async function configExistingModules(tmModules) { + let options = tmModules.map(m => `${m.name} at ${m.address}`); + let index = readlineSync.keyInSelect(options, 'Which module do you want to config? ', { cancel: 'RETURN' }); + console.log('Selected:', index !== -1 ? options[index] : 'RETURN', '\n'); + let moduleNameSelected = index !== -1 ? tmModules[index].name : 'RETURN'; + + switch (moduleNameSelected) { + case 'GeneralTransferManager': + currentTransferManager = new web3.eth.Contract(abis.generalTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await generalTransferManager(); + break; + case 'ManualApprovalTransferManager': + currentTransferManager = new web3.eth.Contract(abis.manualApprovalTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await manualApprovalTransferManager(); + break; + case 'CountTransferManager': + currentTransferManager = new web3.eth.Contract(abis.countTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await countTransferManager(); + break; + case 'PercentageTransferManager': + currentTransferManager = new web3.eth.Contract(abis.percentageTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await percentageTransferManager(); + break; + case 'LockUpTransferManager': + currentTransferManager = new web3.eth.Contract(abis.lockUpTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await lockUpTransferManager(); + break; + case 'BlacklistTransferManager': + currentTransferManager = new web3.eth.Contract(abis.blacklistTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await blacklistTransferManager(); + break; + case 'VolumeRestrictionTM': + currentTransferManager = new web3.eth.Contract(abis.volumeRestrictionTM(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await volumeRestrictionTM(); + break; + } +} + +async function addTransferManagerModule() { + let availableModules = await moduleRegistry.methods.getModulesByTypeAndToken(gbl.constants.MODULES_TYPES.TRANSFER, securityToken.options.address).call(); + let options = await Promise.all(availableModules.map(async function (m) { + let moduleFactoryABI = abis.moduleFactory(); + let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m); + return web3.utils.hexToUtf8(await moduleFactory.methods.name().call()); + })); + + let index = readlineSync.keyInSelect(options, 'Which Transfer Manager module do you want to add? ', { cancel: 'RETURN' }); + if (index != -1 && readlineSync.keyInYNStrict(`Are you sure you want to add ${options[index]} module?`)) { + let bytes = web3.utils.fromAscii('', 16); + switch (options[index]) { + case 'CountTransferManager': + let maxHolderCount = readlineSync.question('Enter the maximum no. of holders the SecurityToken is allowed to have: '); + let configureCountTM = abis.countTransferManager().find(o => o.name === 'configure' && o.type === 'function'); + bytes = web3.eth.abi.encodeFunctionCall(configureCountTM, [maxHolderCount]); + break; + case 'PercentageTransferManager': + let maxHolderPercentage = toWeiPercentage(readlineSync.question('Enter the maximum amount of tokens in percentage that an investor can hold: ', { + limit: function (input) { + return (parseInt(input) > 0 && parseInt(input) <= 100); + }, + limitMessage: "Must be greater than 0 and less than 100" + })); + let allowPercentagePrimaryIssuance = readlineSync.keyInYNStrict(`Do you want to ignore transactions which are part of the primary issuance? `); + let configurePercentageTM = abis.percentageTransferManager().find(o => o.name === 'configure' && o.type === 'function'); + bytes = web3.eth.abi.encodeFunctionCall(configurePercentageTM, [maxHolderPercentage, allowPercentagePrimaryIssuance]); + 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); + let receipt = await common.sendTransaction(addModuleAction); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); + console.log(chalk.green(`Module deployed at address: ${event._module}`)); + } +} + +async function generalTransferManager() { + console.log('\n', chalk.blue(`General Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + + let moduleFactoryABI = abis.moduleFactory(); + let factoryAddress = await currentTransferManager.methods.factory().call(); + let moduleFactory = new web3.eth.Contract(moduleFactoryABI, factoryAddress); + let moduleVersion = await moduleFactory.methods.version().call(); + + // Show current data + let displayIssuanceAddress = await currentTransferManager.methods.issuanceAddress().call(); + let displaySigningAddress = await currentTransferManager.methods.signingAddress().call(); + let displayAllowAllTransfers = await currentTransferManager.methods.allowAllTransfers().call(); + let displayAllowAllWhitelistTransfers = await currentTransferManager.methods.allowAllWhitelistTransfers().call(); + let displayAllowAllWhitelistIssuances = await currentTransferManager.methods.allowAllWhitelistIssuances().call(); + let displayAllowAllBurnTransfers = await currentTransferManager.methods.allowAllBurnTransfers().call(); + let displayDefaults; + let displayInvestors; + if (moduleVersion != '1.0.0') { + displayDefaults = await currentTransferManager.methods.defaults().call(); + displayInvestors = await currentTransferManager.methods.getInvestors().call(); + } + console.log(`- Issuance address: ${displayIssuanceAddress}`); + console.log(`- Signing address: ${displaySigningAddress}`); + console.log(`- Allow all transfers: ${displayAllowAllTransfers ? `YES` : `NO`}`); + console.log(`- Allow all whitelist transfers: ${displayAllowAllWhitelistTransfers ? `YES` : `NO`}`); + console.log(`- Allow all whitelist issuances: ${displayAllowAllWhitelistIssuances ? `YES` : `NO`}`); + console.log(`- Allow all burn transfers: ${displayAllowAllBurnTransfers ? `YES` : `NO`}`); + if (displayDefaults) { + console.log(`- Default times:`); + console.log(` - From time: ${displayDefaults.fromTime} (${moment.unix(displayDefaults.fromTime).format('MMMM Do YYYY, HH:mm:ss')})`); + console.log(` - To time: ${displayDefaults.toTime} (${moment.unix(displayDefaults.toTime).format('MMMM Do YYYY, HH:mm:ss')})`); + } + if (displayInvestors) { + console.log(`- Investors: ${displayInvestors.length}`); + } + // ------------------ + + let options = []; + if (displayInvestors && displayInvestors.length > 0) { + options.push(`Show investors`, `Show whitelist data`); + } + options.push('Modify whitelist', 'Modify whitelist from CSV') /*'Modify Whitelist Signed',*/ + if (displayDefaults) { + options.push('Change the default times used when they are zero'); + } + options.push(`Change issuance address`, 'Change signing address'); + + if (displayAllowAllTransfers) { + options.push('Disallow all transfers'); + } else { + options.push('Allow all transfers'); + } + if (displayAllowAllWhitelistTransfers) { + options.push('Disallow all whitelist transfers'); + } else { + options.push('Allow all whitelist transfers'); + } + if (displayAllowAllWhitelistIssuances) { + options.push('Disallow all whitelist issuances'); + } else { + options.push('Allow all whitelist issuances'); + } + if (displayAllowAllBurnTransfers) { + options.push('Disallow all burn transfers'); + } else { + options.push('Allow all burn transfers'); + } + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case `Show investors`: + console.log('***** List of investors on whitelist *****'); + displayInvestors.map(i => console.log(i)); + break; + case `Show whitelist data`: + let investorsToShow = readlineSync.question(`Enter the addresses of the investors you want to show (i.e: addr1,addr2,addr3) or leave empty to show them all: `, { + limit: function (input) { + return input === '' || input.split(",").every(a => web3.utils.isAddress(a)); + }, + limitMessage: `All addresses must be valid` + }); + if (investorsToShow === '') { + let whitelistData = await currentTransferManager.methods.getAllInvestorsData().call(); + showWhitelistTable(whitelistData[0], whitelistData[1], whitelistData[2], whitelistData[3], whitelistData[4]); + } else { + let investorsArray = investorsToShow.split(','); + let whitelistData = await currentTransferManager.methods.getInvestorsData(investorsArray).call(); + showWhitelistTable(investorsArray, whitelistData[0], whitelistData[1], whitelistData[2], whitelistData[3]); + } + break; + case 'Change the default times used when they are zero': + let fromTimeDefault = readlineSync.questionInt(`Enter the default time (Unix Epoch time) used when fromTime is zero: `); + let toTimeDefault = readlineSync.questionInt(`Enter the default time (Unix Epoch time) used when toTime is zero: `); + let changeDefaultsAction = currentTransferManager.methods.changeDefaults(fromTimeDefault, toTimeDefault); + let changeDefaultsReceipt = await common.sendTransaction(changeDefaultsAction); + let changeDefaultsEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeDefaultsReceipt.logs, 'ChangeDefaults'); + console.log(chalk.green(`Default times have been updated successfully!`)); + break; + case 'Modify whitelist': + let investor = readlineSync.question('Enter the address to whitelist: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + 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 oneYearFromNow = Math.floor(Date.now() / 1000 + (60 * 60 * 24 * 365)); + let expiryTime = readlineSync.questionInt(`Enter the time until the investors KYC will be valid (after this time expires, the investor must re-do KYC) (1 year from now = ${oneYearFromNow}): `, { defaultInput: oneYearFromNow }); + let canBuyFromSTO = readlineSync.keyInYNStrict('Can the investor buy from security token offerings?'); + let modifyWhitelistAction = currentTransferManager.methods.modifyWhitelist(investor, fromTime, toTime, expiryTime, canBuyFromSTO); + let modifyWhitelistReceipt = await common.sendTransaction(modifyWhitelistAction); + if (moduleVersion != '1.0.0') { + let modifyWhitelistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyWhitelistReceipt.logs, 'ModifyWhitelist'); + console.log(chalk.green(`${modifyWhitelistEvent._investor} has been whitelisted sucessfully!`)); + } else { + console.log(chalk.green(`${investor} has been whitelisted sucessfully!`)); + } + break; + case 'Modify whitelist from CSV': + await modifyWhitelistInBatch(); + break; + /* + case 'Modify Whitelist Signed': + let investorSigned = readlineSync.question('Enter the address to whitelist: ', { + limit: function(input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let fromTimeSigned = readlineSync.questionInt('Enter the time (Unix Epoch time) when the sale lockup period ends and the investor can freely sell his tokens: '); + let toTimeSigned = readlineSync.questionInt('Enter the time (Unix Epoch time) when the purchase lockup period ends and the investor can freely purchase tokens from others: '); + let expiryTimeSigned = readlineSync.questionInt('Enter the time till investors KYC will be validated (after that investor need to do re-KYC): '); + let vSigned = readlineSync.questionInt('Enter v: '); + let rSigned = readlineSync.question('Enter r: '); + let sSigned = readlineSync.question('Enter s: '); + let canBuyFromSTOSigned = readlineSync.keyInYNStrict('Can the investor buy from security token offerings?'); + let modifyWhitelistSignedAction = currentTransferManager.methods.modifyWhitelistSigned(investorSigned, fromTimeSigned, toTimeSigned, expiryTimeSigned, canBuyFromSTOSigned); + let modifyWhitelistSignedReceipt = await common.sendTransaction(Issuer, modifyWhitelistSignedAction, defaultGasPrice); + let modifyWhitelistSignedEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyWhitelistSignedReceipt.logs, 'ModifyWhitelist'); + console.log(chalk.green(`${ modifyWhitelistSignedEvent._investor } has been whitelisted sucessfully!`)); + break; + */ + case 'Change issuance address': + let issuanceAddress = readlineSync.question('Enter the new issuance address: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let changeIssuanceAddressAction = currentTransferManager.methods.changeIssuanceAddress(issuanceAddress); + let changeIssuanceAddressReceipt = await common.sendTransaction(changeIssuanceAddressAction); + let changeIssuanceAddressEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeIssuanceAddressReceipt.logs, 'ChangeIssuanceAddress'); + console.log(chalk.green(`${changeIssuanceAddressEvent._issuanceAddress} is the new address for the issuance!`)); + break; + case 'Change signing address': + let signingAddress = readlineSync.question('Enter the new signing address: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let changeSigningAddressAction = currentTransferManager.methods.changeSigningAddress(signingAddress); + let changeSigningAddressReceipt = await common.sendTransaction(changeSigningAddressAction); + let changeSigningAddressEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeSigningAddressReceipt.logs, 'ChangeSigningAddress'); + console.log(chalk.green(`${changeSigningAddressEvent._signingAddress} is the new address for the signing!`)); + break; + case 'Allow all transfers': + case 'Disallow all transfers': + let changeAllowAllTransfersAction = currentTransferManager.methods.changeAllowAllTransfers(!displayAllowAllTransfers); + let changeAllowAllTransfersReceipt = await common.sendTransaction(changeAllowAllTransfersAction); + let changeAllowAllTransfersEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllTransfersReceipt.logs, 'AllowAllTransfers'); + if (changeAllowAllTransfersEvent._allowAllTransfers) { + console.log(chalk.green(`All transfers are allowed!`)); + } else { + console.log(chalk.green(`Transfers are restricted!`)); + } + break; + case 'Allow all whitelist transfers': + case 'Disallow all whitelist transfers': + let changeAllowAllWhitelistTransfersAction = currentTransferManager.methods.changeAllowAllWhitelistTransfers(!displayAllowAllWhitelistTransfers); + let changeAllowAllWhitelistTransfersReceipt = await common.sendTransaction(changeAllowAllWhitelistTransfersAction); + let changeAllowAllWhitelistTransfersEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllWhitelistTransfersReceipt.logs, 'AllowAllWhitelistTransfers'); + if (changeAllowAllWhitelistTransfersEvent._allowAllWhitelistTransfers) { + console.log(chalk.green(`Time locks from whitelist are ignored for transfers!`)); + } else { + console.log(chalk.green(`Transfers are restricted by time locks from whitelist!`)); + } + break; + case 'Allow all whitelist issuances': + case 'Disallow all whitelist issuances': + let changeAllowAllWhitelistIssuancesAction = currentTransferManager.methods.changeAllowAllWhitelistIssuances(!displayAllowAllWhitelistIssuances); + let changeAllowAllWhitelistIssuancesReceipt = await common.sendTransaction(changeAllowAllWhitelistIssuancesAction); + let changeAllowAllWhitelistIssuancesEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllWhitelistIssuancesReceipt.logs, 'AllowAllWhitelistIssuances'); + if (changeAllowAllWhitelistIssuancesEvent._allowAllWhitelistIssuances) { + console.log(chalk.green(`Time locks from whitelist are ignored for issuances!`)); + } else { + console.log(chalk.green(`Issuances are restricted by time locks from whitelist!`)); + } + break; + case 'Allow all burn transfers': + case 'Disallow all burn transfers': + let changeAllowAllBurnTransfersAction = currentTransferManager.methods.changeAllowAllBurnTransfers(!displayAllowAllBurnTransfers); + let changeAllowAllBurnTransfersReceipt = await common.sendTransaction(changeAllowAllBurnTransfersAction); + let changeAllowAllBurnTransfersEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeAllowAllBurnTransfersReceipt.logs, 'AllowAllBurnTransfers'); + if (changeAllowAllBurnTransfersEvent._allowAllWhitelistTransfers) { + console.log(chalk.green(`To burn tokens is allowed!`)); + } else { + console.log(chalk.green(`The burning mechanism is deactivated!`)); + } + break; + case 'RETURN': + return; + } + + await generalTransferManager(); +} + +function showWhitelistTable(investorsArray, fromTimeArray, toTimeArray, expiryTimeArray, canBuyFromSTOArray) { + let dataTable = [['Investor', 'From time', 'To time', 'KYC expiry date', 'Restricted']]; + for (let i = 0; i < investorsArray.length; i++) { + dataTable.push([ + investorsArray[i], + moment.unix(fromTimeArray[i]).format('MM/DD/YYYY HH:mm'), + moment.unix(toTimeArray[i]).format('MM/DD/YYYY HH:mm'), + moment.unix(expiryTimeArray[i]).format('MM/DD/YYYY HH:mm'), + canBuyFromSTOArray[i] ? 'YES' : 'NO' + ]); + } + console.log(); + console.log(table(dataTable)); +} + +async function modifyWhitelistInBatch(_csvFilePath, _batchSize) { + let csvFilePath; + if (typeof _csvFilePath === 'undefined') { + csvFilePath = readlineSync.question(`Enter the path for csv data file (${WHITELIST_DATA_CSV}): `, { + defaultInput: WHITELIST_DATA_CSV + }); + } else { + csvFilePath = _csvFilePath; + } + let batchSize; + if (typeof _batchSize === 'undefined') { + 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 + }); + } else { + batchSize = _batchSize; + } + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => + web3.utils.isAddress(row[0]) && + moment.unix(row[1]).isValid() && + moment.unix(row[2]).isValid() && + moment.unix(row[3]).isValid() && + typeof row[4] === 'boolean' + ); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, fromTimesArray, toTimesArray, expiryTimeArray, canBuyFromSTOArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify whitelist to accounts: \n\n`, investorArray[batch], '\n'); + let action = currentTransferManager.methods.modifyWhitelistMulti(investorArray[batch], fromTimesArray[batch], toTimesArray[batch], expiryTimeArray[batch], canBuyFromSTOArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify whitelist 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 manualApprovalTransferManager() { + console.log('\n', chalk.blue(`Manual Approval Transfer Manager at ${currentTransferManager.options.address} `), '\n'); + + let totalApprovals = await currentTransferManager.methods.getTotalApprovalsLength().call(); + console.log(`- Current active approvals: ${totalApprovals}`); + + let matmOptions = [ + MATM_MENU_ADD, + MATM_MENU_MANAGE, + MATM_MENU_EXPLORE, + MATM_MENU_OPERATE + ]; + + let index = readlineSync.keyInSelect(matmOptions, 'What do you want to do?', { + cancel: 'RETURN' + }); + let optionSelected = index != -1 ? matmOptions[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + + switch (optionSelected) { + case MATM_MENU_ADD: + await matmAdd(); + break; + case MATM_MENU_MANAGE: + await matmManage(); + break; + case MATM_MENU_EXPLORE: + await matmExplore(); + break; + case MATM_MENU_OPERATE: + await matmOperate(); + break; + case 'RETURN': + return; + } + + await manualApprovalTransferManager(); +} + +async function matmAdd() { + let from = readlineSync.question('Enter the address from which transfers will be approved: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let to = readlineSync.question('Enter the address to which transfers will be approved: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + if (!await getManualApproval(from, to)) { + let description = readlineSync.question('Enter the description for the manual approval: ', { + limit: function (input) { + return input != "" && getBinarySize(input) < 33 + }, + limitMessage: "Description is required" + }); + let allowance = readlineSync.question('Enter the amount of tokens which will be approved: '); + let oneHourFromNow = Math.floor(Date.now() / 1000 + 3600); + let expiryTime = readlineSync.questionInt(`Enter the time (Unix Epoch time) until which the transfer is allowed (1 hour from now = ${oneHourFromNow}): `, { defaultInput: oneHourFromNow }); + let addManualApprovalAction = currentTransferManager.methods.addManualApproval(from, to, web3.utils.toWei(allowance), expiryTime, web3.utils.fromAscii(description)); + let addManualApprovalReceipt = await common.sendTransaction(addManualApprovalAction); + let addManualApprovalEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addManualApprovalReceipt.logs, 'AddManualApproval'); + console.log(chalk.green(`Manual approval has been added successfully!`)); + } else { + console.log(chalk.red(`A manual approval already exists from ${from} to ${to}. Revoke it first if you want to add a new one or modify the existing one.`)); + } +} + +async function matmManage() { + + let manageOptions = [ + MATM_MENU_MANAGE_INCRESE, + MATM_MENU_MANAGE_DECREASE, + MATM_MENU_MANAGE_TIME, + MATM_MENU_MANAGE_REVOKE + ]; + + let getApprovals = await getApprovalsArray(); + + if (getApprovals.length > 0) { + let options = [] + getApprovals.forEach((item) => { + options.push(`${web3.utils.toAscii(item.description)}\n From: ${item.from}\n To: ${item.to}\n Amount: ${web3.utils.fromWei(item.allowance)} ${tokenSymbol}\n Expiry date: ${moment.unix(item.expiryTime).format('MM/DD/YYYY HH:mm')}\n`) + }) + + let index = readlineSync.keyInSelect(options, 'Select an existing approval: ', { + cancel: 'RETURN' + }); + let optionSelected = index != -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + + if (optionSelected !== 'RETURN') { + let selectedApproval = getApprovals[index]; + + let index2 = readlineSync.keyInSelect(manageOptions, 'What do you want to do?', { + cancel: 'RETURN' + }); + let optionSelected2 = index2 != -1 ? manageOptions[index2] : 'RETURN'; + console.log('Selected:', optionSelected2, '\n'); + + if (optionSelected2 !== 'RETURN') { + switch (optionSelected2) { + case MATM_MENU_MANAGE_INCRESE: + await matmManageIncrese(selectedApproval); + break; + case MATM_MENU_MANAGE_DECREASE: + await matmManageDecrease(selectedApproval); + break; + case MATM_MENU_MANAGE_TIME: + await matmManageTimeOrDescription(selectedApproval); + break; + case MATM_MENU_MANAGE_REVOKE: + await matmManageRevoke(selectedApproval); + break; + } + } + } + } else { + console.log(chalk.yellow(`There are no existing approvals to show`)); + } +} + +async function matmExplore() { + let getApprovals = await getApprovalsArray(); + getApprovals.forEach((item) => { + printMatmRow(item.from, item.to, item.allowance, item.expiryTime, item.description); + }) +} + +async function matmOperate() { + let operateOptions = [ + MATM_MENU_OPERATE_ADD, + MATM_MENU_OPERATE_MODIFY, + MATM_MENU_OPERATE_REVOKE + ]; + + let index = readlineSync.keyInSelect(operateOptions, 'What do you want to do?', { + cancel: 'RETURN' + }); + let optionSelected = index != -1 ? operateOptions[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + + switch (optionSelected) { + case MATM_MENU_OPERATE_ADD: + await addManualApproveInBatch(); + break; + case MATM_MENU_OPERATE_MODIFY: + await modifyManualApproveInBatch(); + break; + case MATM_MENU_OPERATE_REVOKE: + await revokeManualApproveInBatch(); + break; + } +} + +async function matmManageIncrese(selectedApproval) { + let allowance = readlineSync.question(`Enter a value to increase allowance (current allowance = ${web3.utils.fromWei(selectedApproval.allowance)}): `, { + limit: function (input) { + return parseFloat(input) > 0 + }, + limitMessage: "Amount must be bigger than 0" + }); + + if (readlineSync.keyInYNStrict(`Do you want to modify expiry time or description?`)) { + let { expiryTime, description } = readExpiryTimeAndDescription(selectedApproval); + selectedApproval.expiryTime = expiryTime; + selectedApproval.description = web3.utils.fromAscii(description); + } + + let modifyManualApprovalAction = currentTransferManager.methods.modifyManualApproval(selectedApproval.from, selectedApproval.to, parseInt(selectedApproval.expiryTime), web3.utils.toWei(allowance), selectedApproval.description, 1); + await common.sendTransaction(modifyManualApprovalAction); + console.log(chalk.green(`The approval allowance has been increased successfully!`)); +} + +async function matmManageDecrease(selectedApproval) { + let allowance = readlineSync.question(`Enter a value to decrease allowance (current allowance = ${web3.utils.fromWei(selectedApproval.allowance)}): `, { + limit: function (input) { + return parseFloat(input) > 0 + }, + limitMessage: "Amount must be bigger than 0" + }); + + if (readlineSync.keyInYNStrict(`Do you want to modify expiry time or description?`)) { + let { expiryTime, description } = readExpiryTimeAndDescription(selectedApproval); + selectedApproval.expiryTime = expiryTime; + selectedApproval.description = web3.utils.fromAscii(description); + } + + let modifyManualApprovalAction = currentTransferManager.methods.modifyManualApproval(selectedApproval.from, selectedApproval.to, parseInt(selectedApproval.expiryTime), web3.utils.toWei(allowance), selectedApproval.description, 0); + await common.sendTransaction(modifyManualApprovalAction); + console.log(chalk.green(`The approval allowance has been decreased successfully!`)); +} + +async function matmManageTimeOrDescription(selectedApproval) { + let { expiryTime, description } = readExpiryTimeAndDescription(selectedApproval); + + let modifyManualApprovalAction = currentTransferManager.methods.modifyManualApproval(selectedApproval.from, selectedApproval.to, parseInt(expiryTime), selectedApproval.allowance, web3.utils.fromAscii(description), 2); + await common.sendTransaction(modifyManualApprovalAction); + console.log(chalk.green(`The approval expiry time has been modified successfully!`)); +} + +function readExpiryTimeAndDescription(selectedApproval) { + let expiryTime = readlineSync.questionInt(`Enter the new expiry time (Unix Epoch time) until which the transfer is allowed or leave empty to keep the current (${selectedApproval.expiryTime}): `, { + limit: function (input) { + return parseFloat(input) > 0; + }, + limitMessage: "Enter Unix Epoch time", + defaultInput: selectedApproval.expiryTime + }); + let description = readlineSync.question(`Enter the new description for the manual approval or leave empty to keep the current (${web3.utils.toAscii(selectedApproval.description)}): `, { + limit: function (input) { + return input != "" && getBinarySize(input) < 33; + }, + limitMessage: "Description is required" + }); + return { expiryTime, description }; +} + +async function matmManageRevoke(selectedApproval) { + let modifyManualApprovalAction = currentTransferManager.methods.revokeManualApproval(selectedApproval.from, selectedApproval.to); + await common.sendTransaction(modifyManualApprovalAction); + console.log(chalk.green(`The approval has been revoked successfully!`)); +} + +async function getApprovalsArray() { + let address = readlineSync.question('Enter an address to filter or leave empty to get all the approvals: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address", + defaultInput: gbl.constants.ADDRESS_ZERO + }); + if (address == gbl.constants.ADDRESS_ZERO) { + return await getApprovals(); + } else { + let approvals = await getApprovalsToAnAddress(address); + if (!approvals.length) { + console.log(chalk.red(`\nThe address is not listed\n`)) + } + return approvals; + } +} + +function printMatmRow(from, to, allowance, time, description) { + console.log(`\nDescription: ${web3.utils.toAscii(description)}\nFrom ${from} to ${to}\nAllowance: ${web3.utils.fromWei(allowance)}\nExpiry time: ${moment.unix(time).format('MMMM Do YYYY HH:mm')}\n`); +} + +async function getApprovals() { + function ApprovalDetail(_from, _to, _allowance, _expiryTime, _description) { + this.from = _from; + this.to = _to; + this.allowance = _allowance; + this.expiryTime = _expiryTime; + this.description = _description; + } + + let results = []; + let approvalDetails = await currentTransferManager.methods.getAllApprovals().call(); + for (let i = 0; i < approvalDetails[0].length; i++) { + results.push(new ApprovalDetail(approvalDetails[0][i], approvalDetails[1][i], approvalDetails[2][i], approvalDetails[3][i], approvalDetails[4][i])); + } + return results; +} + +async function getApprovalsToAnAddress(address) { + function ApprovalDetail(_from, _to, _allowance, _expiryTime, _description) { + this.from = _from; + this.to = _to; + this.allowance = _allowance; + this.expiryTime = _expiryTime; + this.description = _description; + } + + let results = []; + let approvals = await currentTransferManager.methods.getActiveApprovalsToUser(address).call(); + for (let i = 0; i < approvals[0].length; i++) { + results.push(new ApprovalDetail(approvals[0][i], approvals[1][i], approvals[2][i], approvals[3][i], approvals[4][i])); + } + return results; +} + +async function getManualApproval(_from, _to) { + let result = null; + + let manualApproval = await currentTransferManager.methods.getApprovalDetails(_from, _to).call(); + if ((manualApproval[0] >= new Date()) && (manualApproval[1] != 0)) { + result = manualApproval; + } + return result; +} + +async function matmGenericCsv(path, f) { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${path}): `, { + defaultInput: path + }); + 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 => f(row)); + 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(',')} `)); + } + return common.splitIntoBatches(validData, batchSize); +} + +async function addManualApproveInBatch() { + + var f = (row) => { + return (web3.utils.isAddress(row[0]) && + web3.utils.isAddress(row[1]) && + parseFloat(row[2]) > 0 && + moment.unix(row[3]).isValid() && + typeof row[4] === 'string' && + getBinarySize(row[4]) < 33) + } + + let batches = await matmGenericCsv(ADD_MANUAL_APPROVAL_DATA_CSV, f) + + let [fromArray, toArray, allowanceArray, expiryArray, descriptionArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add manual approvals: \n\n`, descriptionArray[batch], '\n'); + descriptionArray[batch] = descriptionArray[batch].map(d => web3.utils.fromAscii(d)); + allowanceArray[batch] = allowanceArray[batch].map(a => web3.utils.toWei(new web3.utils.BN(a))); + let action = await currentTransferManager.methods.addManualApprovalMulti(fromArray[batch], toArray[batch], allowanceArray[batch], expiryArray[batch], descriptionArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple manual approvals 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 revokeManualApproveInBatch() { + + var f = (row) => { + return (web3.utils.isAddress(row[0]) && + web3.utils.isAddress(row[1])) + } + + let batches = await matmGenericCsv(REVOKE_MANUAL_APPROVAL_DATA_CSV, f) + + let [fromArray, toArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to revoke manual approvals`, '\n'); + let action = await currentTransferManager.methods.revokeManualApprovalMulti(fromArray[batch], toArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Revoke multip;e manual approvals 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 modifyManualApproveInBatch() { + + var f = (row) => { + return (web3.utils.isAddress(row[0]) && + web3.utils.isAddress(row[1]) && + moment.unix(row[2]).isValid() && + parseFloat(row[3]) > 0 && + typeof row[4] === 'string' && + getBinarySize(row[4]) < 33 && + typeof parseInt(row[5])) === 'number' + } + + let batches = await matmGenericCsv(MODIFY_MANUAL_APPROVAL_DATA_CSV, f) + + let [fromArray, toArray, expiryArray, allowanceArray, descriptionArray, changesArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify manual approvals: \n\n`, descriptionArray[batch], '\n'); + descriptionArray[batch] = descriptionArray[batch].map(d => web3.utils.fromAscii(d)); + allowanceArray[batch] = allowanceArray[batch].map(a => web3.utils.toWei(new web3.utils.BN(a))); + changesArray[batch] = changesArray[batch].map(c => parseInt(c)); + let action = await currentTransferManager.methods.modifyManualApprovalMulti(fromArray[batch], toArray[batch], expiryArray[batch], allowanceArray[batch], descriptionArray[batch], changesArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple manual approvals 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 getBinarySize(string) { + return Buffer.byteLength(string, 'utf8'); +} + +async function countTransferManager() { + console.log('\n', chalk.blue(`Count Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + + // Show current data + let displayMaxHolderCount = await currentTransferManager.methods.maxHolderCount().call(); + + console.log(`- Max holder count: ${displayMaxHolderCount}`); + + let options = ['Change max holder count'] + 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 max holder count': + let maxHolderCount = readlineSync.question('Enter the maximum no. of holders the SecurityToken is allowed to have: '); + let changeHolderCountAction = currentTransferManager.methods.changeHolderCount(maxHolderCount); + let changeHolderCountReceipt = await common.sendTransaction(changeHolderCountAction); + let changeHolderCountEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeHolderCountReceipt.logs, 'ModifyHolderCount'); + console.log(chalk.green(`Max holder count has been set to ${changeHolderCountEvent._newHolderCount} sucessfully!`)); + break; + case 'RETURN': + return; + } + + await countTransferManager(); +} + +async function percentageTransferManager() { + console.log('\n', chalk.blue(`Percentage Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + + // Show current data + let displayMaxHolderPercentage = await currentTransferManager.methods.maxHolderPercentage().call(); + let displayAllowPrimaryIssuance = await currentTransferManager.methods.allowPrimaryIssuance().call(); + + console.log(`- Max holder percentage: ${fromWeiPercentage(displayMaxHolderPercentage)}%`); + console.log(`- Allow primary issuance: ${displayAllowPrimaryIssuance ? `YES` : `NO`}`); + + let options = ['Change max holder percentage', 'Check if investor is whitelisted', 'Modify whitelist', 'Modify whitelist from CSV']; + if (displayAllowPrimaryIssuance) { + options.push('Disallow primary issuance'); + } else { + options.push('Allow primary issuance'); + } + 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 max holder percentage': + let maxHolderPercentage = toWeiPercentage(readlineSync.question('Enter the maximum amount of tokens in percentage that an investor can hold: ', { + limit: function (input) { + return (parseInt(input) > 0 && parseInt(input) <= 100); + }, + limitMessage: "Must be greater than 0 and less than 100" + })); + let changeHolderPercentageAction = currentTransferManager.methods.changeHolderPercentage(maxHolderPercentage); + let changeHolderPercentageReceipt = await common.sendTransaction(changeHolderPercentageAction); + let changeHolderPercentageEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, changeHolderPercentageReceipt.logs, 'ModifyHolderPercentage'); + console.log(chalk.green(`Max holder percentage has been set to ${fromWeiPercentage(changeHolderPercentageEvent._newHolderPercentage)} successfully!`)); + break; + case 'Check if investor is whitelisted': + let investorToCheck = readlineSync.question('Enter the address of the investor: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let isWhitelisted = await currentTransferManager.methods.whitelist(investorToCheck).call(); + if (isWhitelisted) { + console.log(chalk.green(`${investorToCheck} is whitelisted!`)); + } else { + console.log(chalk.yellow(`${investorToCheck} is not whitelisted!`)); + } + break; + case 'Modify whitelist': + let valid = !!readlineSync.keyInSelect(['Remove investor from whitelist', 'Add investor to whitelist'], 'How do you want to do? ', { cancel: false }); + let investorToWhitelist = readlineSync.question('Enter the address of the investor: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let modifyWhitelistAction = currentTransferManager.methods.modifyWhitelist(investorToWhitelist, valid); + let modifyWhitelistReceipt = await common.sendTransaction(modifyWhitelistAction); + let modifyWhitelistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyWhitelistReceipt.logs, 'ModifyWhitelist'); + if (modifyWhitelistEvent._valid) { + console.log(chalk.green(`${modifyWhitelistEvent._investor} has been added to the whitelist sucessfully!`)); + } else { + console.log(chalk.green(`${modifyWhitelistEvent._investor} has been removed from the whitelist sucessfully!`)); + } + break; + case 'Modify whitelist from CSV': + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${PERCENTAGE_WHITELIST_DATA_CSV}): `, { + defaultInput: PERCENTAGE_WHITELIST_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => web3.utils.isAddress(row[0]) && typeof row[1] === 'boolean'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')}`)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, isWhitelistedArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify whitelist accounts:\n\n`, investorArray[batch], '\n'); + let action = await currentTransferManager.methods.modifyWhitelistMulti(investorArray[batch], isWhitelistedArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify whitelist 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`); + } + break; + case 'Allow primary issuance': + case 'Disallow primary issuance': + let setAllowPrimaryIssuanceAction = currentTransferManager.methods.setAllowPrimaryIssuance(!displayAllowPrimaryIssuance); + let setAllowPrimaryIssuanceReceipt = await common.sendTransaction(setAllowPrimaryIssuanceAction); + let setAllowPrimaryIssuanceEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, setAllowPrimaryIssuanceReceipt.logs, 'SetAllowPrimaryIssuance'); + if (setAllowPrimaryIssuanceEvent._allowPrimaryIssuance) { + console.log(chalk.green(`Transactions which are part of the primary issuance will be ignored!`)); + } else { + console.log(chalk.green(`Transactions which are part of the primary issuance will NOT be ignored!`)); + } + break; + } + + await percentageTransferManager(); +} + +async function blacklistTransferManager() { + console.log('\n', chalk.blue(`Blacklist Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + + let currentBlacklists = await currentTransferManager.methods.getAllBlacklists().call(); + console.log(`- Blacklists: ${currentBlacklists.length}`); + + let options = ['Add new blacklist']; + if (currentBlacklists.length > 0) { + options.push('Manage existing blacklist', 'Explore account'); + } + options.push('Delete investors from all blacklists', 'Operate with multiple blacklists'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: "RETURN" }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add new blacklist': + let name = readlineSync.question(`Enter the name of the blacklist type: `, { + limit: function (input) { + return input !== ""; + }, + limitMessage: `Invalid blacklist name` + }); + let minuteFromNow = Math.floor(Date.now() / 1000) + 60; + let startTime = readlineSync.questionInt(`Enter the start date (Unix Epoch time) of the blacklist type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); + let oneDayFromStartTime = startTime + 24 * 60 * 60; + let endTime = readlineSync.questionInt(`Enter the end date (Unix Epoch time) of the blacklist type (1 day from start time = ${oneDayFromStartTime}): `, { defaultInput: oneDayFromStartTime }); + let repeatPeriodTime = readlineSync.questionInt(`Enter the repeat period (days) of the blacklist type, 0 to disable (90 days): `, { defaultInput: 90 }); + if (readlineSync.keyInYNStrict(`Do you want to add an investor to this blacklist type? `)) { + let investor = readlineSync.question(`Enter the address of the investor: `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: `Must be a valid address` + }); + let addInvestorToNewBlacklistAction = currentTransferManager.methods.addInvestorToNewBlacklist( + startTime, + endTime, + web3.utils.toHex(name), + repeatPeriodTime, + investor + ); + let addInvestorToNewBlacklistReceipt = await common.sendTransaction(addInvestorToNewBlacklistAction); + let addNewBlacklistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addInvestorToNewBlacklistReceipt.logs, 'AddBlacklistType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(addNewBlacklistEvent._blacklistName)} blacklist type has been added successfully!`)); + let addInvestorToNewBlacklistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addInvestorToNewBlacklistReceipt.logs, 'AddInvestorToBlacklist'); + console.log(chalk.green(`${addInvestorToNewBlacklistEvent._investor} has been added to ${web3.utils.hexToUtf8(addInvestorToNewBlacklistEvent._blacklistName)} successfully!`)); + } else { + let addBlacklistTypeAction = currentTransferManager.methods.addBlacklistType(startTime, endTime, web3.utils.toHex(name), repeatPeriodTime); + let addBlacklistTypeReceipt = await common.sendTransaction(addBlacklistTypeAction); + let addBlacklistTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addBlacklistTypeReceipt.logs, 'AddBlacklistType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(addBlacklistTypeEvent._blacklistName)} blacklist type has been added successfully!`)); + } + break; + case 'Manage existing blacklist': + let options = currentBlacklists.map(b => web3.utils.hexToUtf8(b)); + let index = readlineSync.keyInSelect(options, 'Which blacklist type do you want to manage? ', { cancel: "RETURN" }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + if (index !== -1) { + await manageExistingBlacklist(currentBlacklists[index]); + } + break; + case 'Explore account': + let account = readlineSync.question(`Enter the address of the investor: `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: `Must be a valid address` + }); + let blacklistNamesToUser = await currentTransferManager.methods.getBlacklistNamesToUser(account).call(); + if (blacklistNamesToUser.length > 0) { + console.log(); + console.log(`**** Blacklists inlcuding ${account} ****`); + blacklistNamesToUser.map(n => console.log(web3.utils.hexToUtf8(n))); + } else { + console.log(chalk.yellow(`No blacklist includes ${account}`)); + } + console.log(); + break; + case 'Delete investors from all blacklists': + let investorsToRemove = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e. addr1,addr2,addr3): `, { + limit: function (input) { + return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(','); + let deleteInvestorFromAllBlacklistAction; + if (investorsToRemove.length === 1) { + deleteInvestorFromAllBlacklistAction = currentTransferManager.methods.deleteInvestorFromAllBlacklist(investorsToRemove[0]); + } else { + deleteInvestorFromAllBlacklistAction = currentTransferManager.methods.deleteInvestorFromAllBlacklistMulti(investorsToRemove); + } + let deleteInvestorFromAllBlacklistReceipt = await common.sendTransaction(deleteInvestorFromAllBlacklistAction); + let deleteInvestorFromAllBlacklistEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, deleteInvestorFromAllBlacklistReceipt.logs, 'DeleteInvestorFromBlacklist'); + deleteInvestorFromAllBlacklistEvents.map(e => console.log(chalk.green(`${e._investor} has been removed from ${web3.utils.hexToUtf8(e._blacklistName)} successfully!`))); + break; + case 'Operate with multiple blacklists': + await operateWithMultipleBlacklists(currentBlacklists); + break; + case 'RETURN': + return; + } + + await blacklistTransferManager(); +} + +async function manageExistingBlacklist(blacklistName) { + // Show current data + let currentBlacklist = await currentTransferManager.methods.blacklists(blacklistName).call(); + let investors = await currentTransferManager.methods.getListOfAddresses(blacklistName).call(); + + console.log(); + console.log(`- Name: ${web3.utils.hexToUtf8(blacklistName)}`); + console.log(`- Start time: ${moment.unix(currentBlacklist.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- End time: ${moment.unix(currentBlacklist.endTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- Span: ${(currentBlacklist.endTime - currentBlacklist.startTime) / 60 / 60 / 24} days`); + console.log(`- Repeat period time: ${currentBlacklist.repeatPeriodTime} days`); + console.log(`- Investors: ${investors.length}`); + // ------------------ + + let options = [ + "Modify properties", + "Show investors", + "Add investors", + "Remove investor", + "Delete this blacklist type" + ]; + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Modify properties': + let minuteFromNow = Math.floor(Date.now() / 1000) + 60; + let startTime = readlineSync.questionInt(`Enter the start date (Unix Epoch time) of the blacklist type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); + let oneDayFromStartTime = startTime + 24 * 60 * 60; + let endTime = readlineSync.questionInt(`Enter the end date (Unix Epoch time) of the blacklist type (1 day from start time = ${oneDayFromStartTime}): `, { defaultInput: oneDayFromStartTime }); + let repeatPeriodTime = readlineSync.questionInt(`Enter the repeat period (days) of the blacklist type, 0 to disable (90 days): `, { defaultInput: 90 }); + let modifyBlacklistTypeAction = currentTransferManager.methods.modifyBlacklistType( + startTime, + endTime, + blacklistName, + repeatPeriodTime + ); + let modifyBlacklistTypeReceipt = await common.sendTransaction(modifyBlacklistTypeAction); + let modifyBlacklistTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyBlacklistTypeReceipt.logs, 'ModifyBlacklistType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(modifyBlacklistTypeEvent._blacklistName)} blacklist type has been modified successfully!`)); + break; + case 'Show investors': + if (investors.length > 0) { + console.log("************ List of investors ************"); + investors.map(i => console.log(i)); + } else { + console.log(chalk.yellow("There are no investors yet")); + } + break; + case 'Add investors': + let investorsToAdd = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e. addr1,addr2,addr3): `, { + limit: function (input) { + return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(","); + let addInvestorToBlacklistAction; + if (investorsToAdd.length === 1) { + addInvestorToBlacklistAction = currentTransferManager.methods.addInvestorToBlacklist(investorsToAdd[0], blacklistName); + } else { + addInvestorToBlacklistAction = currentTransferManager.methods.addInvestorToBlacklistMulti(investorsToAdd, blacklistName); + } + let addInvestorToBlacklistReceipt = await common.sendTransaction(addInvestorToBlacklistAction); + let addInvestorToBlacklistEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, addInvestorToBlacklistReceipt.logs, 'AddInvestorToBlacklist'); + addInvestorToBlacklistEvents.map(e => console.log(chalk.green(`${e._investor} has been added to ${web3.utils.hexToUtf8(e._blacklistName)} successfully!`))); + break; + case "Remove investor": + let investorsToRemove = readlineSync.question(`Enter the address of the investor: `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: `Must be a valid address` + }); + let deleteInvestorFromBlacklistAction = currentTransferManager.methods.deleteInvestorFromBlacklist(investorsToRemove, blacklistName); + let deleteInvestorFromBlacklistReceipt = await common.sendTransaction(deleteInvestorFromBlacklistAction); + let deleteInvestorFromBlacklistEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, deleteInvestorFromBlacklistReceipt.logs, 'DeleteInvestorFromBlacklist'); + console.log(chalk.green(`${deleteInvestorFromBlacklistEvent._investor} has been removed from ${web3.utils.hexToUtf8(deleteInvestorFromBlacklistEvent._blacklistName)} successfully!`)); + break; + case "Delete this blacklist type": + let isEmpty = investors.length === 0; + if (!isEmpty) { + console.log(chalk.yellow(`This blacklist have investors added on it. To delete it you must remove them first.`)); + if (readlineSync.keyInYNStrict(`Do you want to remove them? `)) { + let data = investors.map(i => [i, blacklistName]) + let batches = common.splitIntoBatches(data, gbl.constants.DEFAULT_BATCH_SIZE); + let [investorArray, blacklistNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove the following investors:\n\n`, investorArray[batch], '\n'); + let action = currentTransferManager.methods.deleteMultiInvestorsFromBlacklistMulti(investorArray[batch], blacklistNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove investors from multiple blacklists transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } + isEmpty = true; + } + } + if (isEmpty) { + let deleteBlacklistTypeAction = currentTransferManager.methods.deleteBlacklistType(blacklistName); + let deleteBlacklistTypeReceipt = await common.sendTransaction(deleteBlacklistTypeAction); + let deleteBlacklistTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, deleteBlacklistTypeReceipt.logs, 'DeleteBlacklistType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(deleteBlacklistTypeEvent._blacklistName)} blacklist type has been deleted successfully!`)); + } + return; + case 'RETURN': + return; + } + + await manageExistingBlacklist(blacklistName); +} + +async function operateWithMultipleBlacklists(currentBlacklists) { + let options = ['Add multiple blacklists']; + if (currentBlacklists.length > 0) { + options.push('Modify multiple blacklists'); + } + options.push( + 'Delete multiple blacklists', + 'Add investors to multiple blacklists', + 'Remove investors from multiple blacklists' + ); + + 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 blacklists': + await addBlacklistsInBatch(); + break; + case 'Modify multiple blacklists': + await modifyBlacklistsInBatch(); + break; + case 'Delete multiple blacklists': + await deleteBlacklistsInBatch(); + break; + case 'Add investors to multiple blacklists': + await addInvestorsToBlacklistsInBatch(); + break; + case 'Remove investors from multiple blacklists': + await removeInvestorsFromBlacklistsInBatch(); + break; + } +} + +async function addBlacklistsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_BLACKLIST_DATA_CSV}): `, { + defaultInput: ADD_BLACKLIST_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 => moment.unix(row[0]).isValid() && + moment.unix(row[1]).isValid() && + typeof row[2] === 'string' && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 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 [startTimeArray, endTimeArray, blacklistNameArray, repeatPeriodTimeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add the following blacklists:\n\n`, blacklistNameArray[batch], '\n'); + blacklistNameArray[batch] = blacklistNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.addBlacklistTypeMulti(startTimeArray[batch], endTimeArray[batch], blacklistNameArray[batch], repeatPeriodTimeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple blacklists 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 executeApp(remoteNetwork) { - await global.initialize(remoteNetwork); - +async function modifyBlacklistsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_BLACKLIST_DATA_CSV}): `, { + defaultInput: MODIFY_BLACKLIST_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 => moment.unix(row[0]).isValid() && + moment.unix(row[1]).isValid() && + typeof row[2] === 'string' && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 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 [startTimeArray, endTimeArray, blacklistNameArray, repeatPeriodTimeArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify the following blacklists:\n\n`, blacklistNameArray[batch], '\n'); + blacklistNameArray[batch] = blacklistNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.modifyBlacklistTypeMulti(startTimeArray[batch], endTimeArray[batch], blacklistNameArray[batch], repeatPeriodTimeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple blacklists 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 deleteBlacklistsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${DELETE_BLACKLIST_DATA_CSV}): `, { + defaultInput: DELETE_BLACKLIST_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => typeof row[0] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + + let verifiedData = []; + let unverifiedData = []; + for (const row of validData) { + let blacklistName = row[0]; + let verifiedTransaction = (await currentTransferManager.methods.getListOfAddresses(web3.utils.toHex(blacklistName)).call()).length === 0; + if (verifiedTransaction) { + verifiedData.push(row); + } else { + unverifiedData.push(row); + } + } + + let batches = common.splitIntoBatches(verifiedData, batchSize); + let [blacklistNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to delete the following blacklists:\n\n`, blacklistNameArray[batch], '\n'); + blacklistNameArray[batch] = blacklistNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.deleteBlacklistTypeMulti(blacklistNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Delete multiple blacklists 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`); + } + + if (unverifiedData.length > 0) { + console.log("*****************************************************************************************************************"); + console.log('The following data would failed as these blacklists have investors. They must be empty to be able to delete them.\n'); + console.log(chalk.red(unverifiedData.map(d => `${d[0]}`).join('\n'))); + console.log("*****************************************************************************************************************"); + } +} + +async function addInvestorsToBlacklistsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_INVESTOR_BLACKLIST_DATA_CSV}): `, { + defaultInput: ADD_INVESTOR_BLACKLIST_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, blacklistNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add the following investors:\n\n`, investorArray[batch], '\n'); + blacklistNameArray[batch] = blacklistNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.addMultiInvestorToBlacklistMulti(investorArray[batch], blacklistNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add investors to multiple blacklists 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 makeBatchRequest(calls) { + let batch = new web3.BatchRequest(); + + let promises = calls.map(call => { + return new Promise((res, rej) => { + let req = call.request({ from: Issuer.address }, (err, data) => { + if (err) rej(err); + else res(data) + }); + batch.add(req) + }) + }) + batch.execute() + + return Promise.all(promises) +} + +async function removeInvestorsFromBlacklistsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_INVESTOR_BLACKLIST_DATA_CSV}): `, { + defaultInput: REMOVE_INVESTOR_BLACKLIST_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, blacklistNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove the following investors:\n\n`, investorArray[batch], '\n'); + blacklistNameArray[batch] = blacklistNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.deleteMultiInvestorsFromBlacklistMulti(investorArray[batch], blacklistNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove investors from multiple blacklists 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 volumeRestrictionTM() { + console.log('\n', chalk.blue(`Volume Restriction Transfer Manager at ${currentTransferManager.options.address}`, '\n')); + + let globalDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); + let hasGlobalDailyRestriction = parseInt(globalDailyRestriction.startTime) !== 0; + let globalCustomRestriction = await currentTransferManager.methods.defaultRestriction().call(); + let hasGlobalCustomRestriction = parseInt(globalCustomRestriction.startTime) !== 0; + + console.log(`- Default daily restriction: ${hasGlobalDailyRestriction ? '' : 'None'}`); + if (hasGlobalDailyRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[globalDailyRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${globalDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalDailyRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(globalDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${globalDailyRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(globalDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + } + console.log(`- Default custom restriction: ${hasGlobalCustomRestriction ? '' : 'None'}`); + if (hasGlobalCustomRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[globalCustomRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${globalCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(globalCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(globalCustomRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(globalCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${globalCustomRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(globalCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + } + + let addressesAndRestrictions = await currentTransferManager.methods.getRestrictedData().call(); + console.log(`- Individual restrictions: ${addressesAndRestrictions.allAddresses.length}`); + let exemptedAddresses = await currentTransferManager.methods.getExemptAddress().call(); + console.log(`- Exempted addresses: ${exemptedAddresses.length}`); + + let options = []; + if (addressesAndRestrictions.allAddresses.length > 0) { + options.push('Show restrictions'); + } + if (exemptedAddresses.length > 0) { + options.push('Show exempted addresses'); + } + options.push( + 'Change exempt wallet', + 'Change default restrictions', + 'Change individual restrictions', + 'Explore account', + 'Operate with multiple restrictions' + ); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Show restrictions': + showRestrictionTable( + addressesAndRestrictions.allAddresses, + addressesAndRestrictions.allowedTokens, + addressesAndRestrictions.typeOfRestriction, + addressesAndRestrictions.rollingPeriodInDays, + addressesAndRestrictions.startTime, + addressesAndRestrictions.endTime, + ); + break; + case 'Show exempted addresses': + showExemptedAddresses(exemptedAddresses); + break; + case 'Change exempt wallet': + await changeExemptWallet(); + break; + case 'Change default restrictions': + await changeDefaultRestrictions(hasGlobalDailyRestriction, hasGlobalCustomRestriction); + 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(); +} + +function showRestrictionTable(investorArray, amountArray, typeArray, rollingPeriodArray, startTimeArray, endTimeTimeArray) { + let dataTable = [['Investor', 'Maximum transfer (# or %)', 'Rolling period (days)', 'Start date', 'End date']]; + for (let i = 0; i < investorArray.length; i++) { + dataTable.push([ + investorArray[i], + typeArray[i] === "0" ? `${web3.utils.fromWei(amountArray[i])} ${tokenSymbol}` : `${fromWeiPercentage(amountArray[i])}%`, + rollingPeriodArray[i], + moment.unix(startTimeArray[i]).format('MM/DD/YYYY HH:mm'), + moment.unix(endTimeTimeArray[i]).format('MM/DD/YYYY HH:mm') + ]); + } + console.log(); + console.log(table(dataTable)); +} + +function showExemptedAddresses(addresses) { + console.log("*********** Exepmpted addresses ***********"); + addresses.map(i => console.log(i)); +} + +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(hasGlobalDailyRestriction, hasGlobalCustomRestriction) { + let options = []; + if (!hasGlobalDailyRestriction) { + options.push('Add global daily restriction'); + } else { + options.push('Modify global daily restriction', 'Remove global daily restriction'); + } + + if (!hasGlobalCustomRestriction) { + options.push('Add global custom restriction'); + } else { + options.push('Modify global custom restriction', 'Remove global custom restriction'); + } + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add global daily restriction': + let globalDailyRestrictoToAdd = inputRestrictionData(true); + let addGlobalDailyRestrictionAction = currentTransferManager.methods.addDefaultDailyRestriction( + globalDailyRestrictoToAdd.allowedTokens, + globalDailyRestrictoToAdd.startTime, + globalDailyRestrictoToAdd.endTime, + globalDailyRestrictoToAdd.restrictionType + ); + let addGlobalDailyRestrictionReceipt = await common.sendTransaction(addGlobalDailyRestrictionAction); + let addGlobalDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addGlobalDailyRestrictionReceipt.logs, 'AddDefaultDailyRestriction'); + console.log(chalk.green(`Global daily restriction has been added successfully!`)); + break; + case 'Modify global daily restriction': + let globalDailyRestrictoToModify = inputRestrictionData(true); + let modifyGlobalDailyRestrictionAction = currentTransferManager.methods.modifyDefaultDailyRestriction( + globalDailyRestrictoToModify.allowedTokens, + globalDailyRestrictoToModify.startTime, + globalDailyRestrictoToModify.endTime, + globalDailyRestrictoToModify.restrictionType + ); + let modifyGlobalDailyRestrictionReceipt = await common.sendTransaction(modifyGlobalDailyRestrictionAction); + let modifyGlobalDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyGlobalDailyRestrictionReceipt.logs, 'ModifyDefaultDailyRestriction'); + console.log(chalk.green(`Global daily restriction has been modified successfully!`)); + break; + case 'Remove global daily restriction': + let removeGlobalDailyRestrictionAction = currentTransferManager.methods.removeDefaultDailyRestriction(); + let removeGlobalDailyRestrictionReceipt = await common.sendTransaction(removeGlobalDailyRestrictionAction); + let removeGlobalDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeGlobalDailyRestrictionReceipt.logs, 'DefaultDailyRestrictionRemoved'); + console.log(chalk.green(`Global daily restriction has been removed successfully!`)); + break; + case 'Add global custom restriction': + let globalCustomRestrictoToAdd = inputRestrictionData(false); + let addGlobalCustomRestrictionAction = currentTransferManager.methods.addDefaultRestriction( + globalCustomRestrictoToAdd.allowedTokens, + globalCustomRestrictoToAdd.startTime, + globalCustomRestrictoToAdd.rollingPeriodInDays, + globalCustomRestrictoToAdd.endTime, + globalCustomRestrictoToAdd.restrictionType + ); + let addGlobalCustomRestrictionReceipt = await common.sendTransaction(addGlobalCustomRestrictionAction); + let addGlobalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addGlobalCustomRestrictionReceipt.logs, 'AddDefaultRestriction'); + console.log(chalk.green(`Global custom restriction has been added successfully!`)); + break; + case 'Modify global custom restriction': + let globalCustomRestrictoToModify = inputRestrictionData(false); + let modifiyGlobalCustomRestrictionAction = currentTransferManager.methods.modifyDefaultRestriction( + globalCustomRestrictoToModify.allowedTokens, + globalCustomRestrictoToModify.startTime, + globalCustomRestrictoToModify.rollingPeriodInDays, + globalCustomRestrictoToModify.endTime, + globalCustomRestrictoToModify.restrictionType + ); + let modifyGlobalCustomRestrictionReceipt = await common.sendTransaction(modifiyGlobalCustomRestrictionAction); + let modifyGlobalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyGlobalCustomRestrictionReceipt.logs, 'ModifyDefaultRestriction'); + console.log(chalk.green(`Global custom restriction has been modified successfully!`)); + break; + case 'Remove global custom restriction': + let removeGlobalCustomRestrictionAction = currentTransferManager.methods.removeDefaultRestriction(); + let removeGlobalCustomRestrictionReceipt = await common.sendTransaction(removeGlobalCustomRestrictionAction); + let removeGlobalCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeGlobalCustomRestrictionReceipt.logs, 'DefaultRestrictionRemoved'); + console.log(chalk.green(`Global custom restriction has been removed successfully!`)); + break; + } +} + +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 currentCustomRestriction = await currentTransferManager.methods.individualRestriction(holder).call(); + let hasCustomRestriction = parseInt(currentCustomRestriction.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(`- Custom restriction: ${hasCustomRestriction ? '' : 'None'} `); + if (hasCustomRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[currentCustomRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${currentCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(currentCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(currentCustomRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(currentCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${currentCustomRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(currentCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + } + + let options = []; + if (!hasDailyRestriction) { + options.push('Add individual daily restriction'); + } else { + options.push('Modify individual daily restriction', 'Remove individual daily restriction'); + } + + if (!hasCustomRestriction) { + options.push('Add individual custom restriction'); + } else { + options.push('Modify individual custom restriction', 'Remove individual custom restriction'); + } + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add individual daily restriction': + let dailyRestrictonToAdd = inputRestrictionData(true); + let addDailyRestrictionAction = currentTransferManager.methods.addIndividualDailyRestriction( + holder, + dailyRestrictonToAdd.allowedTokens, + dailyRestrictonToAdd.startTime, + dailyRestrictonToAdd.endTime, + dailyRestrictonToAdd.restrictionType + ); + let addDailyRestrictionReceipt = await common.sendTransaction(addDailyRestrictionAction); + let addDailyRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addDailyRestrictionReceipt.logs, 'AddIndividualDailyRestriction'); + console.log(chalk.green(`Daily restriction for ${addDailyRestrictionEvent._holder} has been added successfully!`)); + break; + case 'Modify individual daily restriction': + let dailyRestrictonToModify = inputRestrictionData(true); + let modifyDailyRestrictionAction = currentTransferManager.methods.modifyIndividualDailyRestriction( + holder, + dailyRestrictonToModify.allowedTokens, + dailyRestrictonToModify.startTime, + dailyRestrictonToModify.endTime, + dailyRestrictonToModify.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 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 'Add individual custom restriction': + let restrictonToAdd = inputRestrictionData(false); + let addCustomRestrictionAction = currentTransferManager.methods.addIndividualRestriction( + holder, + restrictonToAdd.allowedTokens, + restrictonToAdd.startTime, + restrictonToAdd.rollingPeriodInDays, + restrictonToAdd.endTime, + restrictonToAdd.restrictionType + ); + let addCustomRestrictionReceipt = await common.sendTransaction(addCustomRestrictionAction); + let addCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addCustomRestrictionReceipt.logs, 'AddIndividualRestriction'); + console.log(chalk.green(`Custom restriction for ${addCustomRestrictionEvent._holder} has been added successfully!`)); + break; + case 'Modify individual custom restriction': + let restrictonToModify = inputRestrictionData(false); + let modifyCustomRestrictionAction = currentTransferManager.methods.modifyIndividualRestriction( + holder, + restrictonToModify.allowedTokens, + restrictonToModify.startTime, + restrictonToModify.rollingPeriodInDays, + restrictonToModify.endTime, + restrictonToModify.restrictionType + ); + let modifyCustomRestrictionReceipt = await common.sendTransaction(modifyCustomRestrictionAction); + let modifyCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyCustomRestrictionReceipt.logs, 'ModifyIndividualRestriction'); + console.log(chalk.green(`Custom restriction for ${modifyCustomRestrictionEvent._holder} has been modified successfully!`)); + break; + case 'Remove individual custom restriction': + let removeCustomRestrictionAction = currentTransferManager.methods.removeIndividualRestriction(holder); + let removeCustomRestrictionReceipt = await common.sendTransaction(removeCustomRestrictionAction); + let removeCustomRestrictionEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeCustomRestrictionReceipt.logs, 'IndividualRestrictionRemoved'); + console.log(chalk.green(`Custom restriction for ${removeCustomRestrictionEvent._holder} has been removed successfully!`)); + break; + case 'RETURN': + return; + } +} + +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 appliyngDailyRestriction = null; + let applyingCustomRestriction = null; + let hasIndividualRestrictions = false; + let isExempted = await currentTransferManager.methods.exemptList(account).call(); + if (!isExempted) { + let individuallDailyRestriction = await currentTransferManager.methods.individualDailyRestriction(account).call(); + if (parseInt(individuallDailyRestriction.endTime) !== 0) { + appliyngDailyRestriction = individuallDailyRestriction; + } + let customRestriction = await currentTransferManager.methods.individualRestriction(account).call(); + if (parseInt(customRestriction.endTime) !== 0) { + applyingCustomRestriction = customRestriction; + } + + hasIndividualRestrictions = applyingCustomRestriction || appliyngDailyRestriction; + + if (!hasIndividualRestrictions) { + let globalDailyRestriction = await currentTransferManager.methods.defaultDailyRestriction().call(); + if (parseInt(globalDailyRestriction.endTime) !== 0) { + appliyngDailyRestriction = globalDailyRestriction; + } + let globalCustomRestriction = await currentTransferManager.methods.defaultRestriction().call(); + if (parseInt(globalCustomRestriction.endTime) === 0) { + applyingCustomRestriction = globalCustomRestriction; + } + } + } + + console.log(`*** Applying restrictions for ${account} ***`, '\n'); + + console.log(`- Daily restriction: ${appliyngDailyRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'}`); + if (appliyngDailyRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[appliyngDailyRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${appliyngDailyRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(appliyngDailyRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(appliyngDailyRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(appliyngDailyRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${appliyngDailyRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(appliyngDailyRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + } + console.log(`- Other restriction: ${applyingCustomRestriction ? (!hasIndividualRestrictions ? 'global' : '') : 'None'} `); + if (applyingCustomRestriction) { + console.log(` Type: ${RESTRICTION_TYPES[applyingCustomRestriction.typeOfRestriction]}`); + console.log(` Allowed tokens: ${applyingCustomRestriction.typeOfRestriction === "0" ? `${web3.utils.fromWei(applyingCustomRestriction.allowedTokens)} ${tokenSymbol}` : `${fromWeiPercentage(applyingCustomRestriction.allowedTokens)}%`}`); + console.log(` Start time: ${moment.unix(applyingCustomRestriction.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(` Rolling period: ${applyingCustomRestriction.rollingPeriodInDays} days`); + console.log(` End time: ${moment.unix(applyingCustomRestriction.endTime).format('MMMM Do YYYY, HH:mm:ss')} `); + } + + if (applyingCustomRestriction || appliyngDailyRestriction) { + 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 'Modify multiple individual daily restrictions': + await modifyDailyRestrictionsInBatch(); + break; + case 'Remove multiple individual daily restrictions': + await removeDailyRestrictionsInBatch(); + break; + case 'Add multiple individual restrictions': + await addCustomRestrictionsInBatch(); + break; + case 'Modify multiple individual restrictions': + await modifyCustomRestrictionsInBatch(); + break; + case 'Remove multiple individual restrictions': + await removeCustomRestrictionsInBatch(); + 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 addCustomRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: ADD_CUSTOM_RESTRICTIONS_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + 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 custom restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + allowanceArray[batch] = allowanceArray[batch].map(n => web3.utils.toWei(n.toString())); + restrictionTypeArray[batch] = restrictionTypeArray[batch].map(n => RESTRICTION_TYPES.indexOf(n)); + let action = currentTransferManager.methods.addIndividualRestrictionMulti(holderArray[batch], allowanceArray[batch], startTimeArray[batch], rollingPeriodArray[batch], endTimeArray[batch], restrictionTypeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple custom restrictions transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function modifyCustomRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: MODIFY_CUSTOM_RESTRICTIONS_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + 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 custom restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + allowanceArray[batch] = allowanceArray[batch].map(n => web3.utils.toWei(n.toString())); + restrictionTypeArray[batch] = restrictionTypeArray[batch].map(n => RESTRICTION_TYPES.indexOf(n)); + let action = currentTransferManager.methods.modifyIndividualRestrictionMulti(holderArray[batch], allowanceArray[batch], startTimeArray[batch], rollingPeriodArray[batch], endTimeArray[batch], restrictionTypeArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple custom restrictions transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function removeCustomRestrictionsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV}): `, { + defaultInput: REMOVE_CUSTOM_RESTRICTIONS_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + 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 custom restrictions to the following accounts: \n\n`, holderArray[batch], '\n'); + let action = currentTransferManager.methods.removeIndividualRestrictionMulti(holderArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove multiple custom restrictions transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +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.question(`Enter the maximum amount of tokens allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); + } else { + restriction.allowedTokens = toWeiPercentage(readlineSync.question(`Enter the maximum percentage of total supply allowed to be traded every ${isDaily ? 'day' : 'rolling period'}: `).toString()); + } + if (isDaily) { + restriction.rollingPeriodInDays = 1; + } 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 month 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; +} + +async function lockUpTransferManager() { + console.log('\n', chalk.blue(`Lockup Transfer Manager at ${currentTransferManager.options.address}`), '\n'); + + let currentLockups = await currentTransferManager.methods.getAllLockups().call(); + console.log(`- Lockups: ${currentLockups.length}`); + + let options = ['Add new lockup']; + if (currentLockups.length > 0) { + options.push('Show all existing lockups', 'Manage existing lockups', 'Explore investor'); + } + options.push('Operate with multiple lockups'); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add new lockup': + let name = readlineSync.question(`Enter the name of the lockup type: `, { + limit: function (input) { + return input !== ""; + }, + limitMessage: `Invalid lockup name` + }); + let lockupAmount = readlineSync.questionInt(`Enter the amount of tokens that will be locked: `); + let minuteFromNow = Math.floor(Date.now() / 1000) + 60; + let startTime = readlineSync.questionInt(`Enter the start time (Unix Epoch time) of the lockup type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); + let lockUpPeriodSeconds = readlineSync.questionInt(`Enter the total period (seconds) of the lockup type (ten minutes = 600): `, { defaultInput: 600 }); + let releaseFrequencySeconds = readlineSync.questionInt(`Enter how often to release a tranche of tokens in seconds (one minute = 60): `, { defaultInput: 60 }); + if (readlineSync.keyInYNStrict(`Do you want to add an investor to this lockup type? `)) { + let investor = readlineSync.question(`Enter the address of the investor: `, { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: `Must be a valid address` + }); + let addNewLockUpToUserAction = currentTransferManager.methods.addNewLockUpToUser( + investor, + web3.utils.toWei(lockupAmount.toString()), + startTime, + lockUpPeriodSeconds, + releaseFrequencySeconds, + web3.utils.toHex(name) + ); + let addNewLockUpToUserReceipt = await common.sendTransaction(addNewLockUpToUserAction); + let addNewLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddNewLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(addNewLockUpToUserEvent._lockupName)} lockup type has been added successfully!`)); + let addLockUpToUserEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addNewLockUpToUserReceipt.logs, 'AddLockUpToUser'); + console.log(chalk.green(`${addLockUpToUserEvent._userAddress} has been added to ${web3.utils.hexToUtf8(addLockUpToUserEvent._lockupName)} successfully!`)); + } else { + let addLockupTypeAction = currentTransferManager.methods.addNewLockUpType(web3.utils.toWei(lockupAmount.toString()), startTime, lockUpPeriodSeconds, releaseFrequencySeconds, web3.utils.toHex(name)); + let addLockupTypeReceipt = await common.sendTransaction(addLockupTypeAction); + let addLockupTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, addLockupTypeReceipt.logs, 'AddNewLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(addLockupTypeEvent._lockupName)} lockup type has been added successfully!`)); + } + break; + case 'Show all existing lockups': + let allLockups = await currentTransferManager.methods.getAllLockupData().call(); + let nameArray = allLockups[0]; + let amountArray = allLockups[1]; + let startTimeArray = allLockups[2]; + let periodSecondsArray = allLockups[3]; + let releaseFrequencySecondsArray = allLockups[4]; + let unlockedAmountsArray = allLockups[5]; + showLockupTable(nameArray, amountArray, startTimeArray, periodSecondsArray, releaseFrequencySecondsArray, unlockedAmountsArray); + break; + case 'Manage existing lockups': + let options = currentLockups.map(b => web3.utils.hexToUtf8(b)); + let index = readlineSync.keyInSelect(options, 'Which lockup type do you want to manage? ', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + if (index !== -1) { + await manageExistingLockups(currentLockups[index]); + } + break; + case 'Explore investor': + let investorToExplore = readlineSync.question('Enter the address you want to explore: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let lockupsToInvestor = await currentTransferManager.methods.getLockupsNamesToUser(investorToExplore).call(); + if (lockupsToInvestor.length > 0) { + let lockedTokenToInvestor = await currentTransferManager.methods.getLockedTokenToUser(investorToExplore).call(); + console.log(chalk.green(`The address ${investorToExplore} has ${web3.utils.fromWei(lockedTokenToInvestor)} ${tokenSymbol} locked across the following ${lockupsToInvestor.length} lockups: `)); + lockupsToInvestor.map(l => console.log(chalk.green(`- ${web3.utils.hexToUtf8(l)}`))); + } else { + console.log(chalk.yellow(`The address ${investorToExplore} has no lockups`)); + } + break; + case 'Operate with multiple lockups': + await operateWithMultipleLockups(currentLockups); + break; + case 'RETURN': + return; + } + + await lockUpTransferManager(); +} + +function showLockupTable(nameArray, amountArray, startTimeArray, periodSecondsArray, releaseFrequencySecondsArray, unlockedAmountsArray) { + let dataTable = [['Lockup Name', `Amount (${tokenSymbol})`, 'Start time', 'Period (seconds)', 'Release frequency (seconds)', `Unlocked amount (${tokenSymbol})`]]; + for (let i = 0; i < nameArray.length; i++) { + dataTable.push([ + web3.utils.hexToUtf8(nameArray[i]), + web3.utils.fromWei(amountArray[i]), + moment.unix(startTimeArray[i]).format('MM/DD/YYYY HH:mm'), + periodSecondsArray[i], + releaseFrequencySecondsArray[i], + web3.utils.fromWei(unlockedAmountsArray[i]) + ]); + } + console.log(); + console.log(table(dataTable)); +} + +async function manageExistingLockups(lockupName) { + console.log('\n', chalk.blue(`Lockup ${web3.utils.hexToUtf8(lockupName)}`), '\n'); + + // Show current data + let currentLockup = await currentTransferManager.methods.getLockUp(lockupName).call(); + let investors = await currentTransferManager.methods.getListOfAddresses(lockupName).call(); + + console.log(`- Amount: ${web3.utils.fromWei(currentLockup.lockupAmount)} ${tokenSymbol}`); + console.log(`- Currently unlocked: ${web3.utils.fromWei(currentLockup.unlockedAmount)} ${tokenSymbol}`); + console.log(`- Start time: ${moment.unix(currentLockup.startTime).format('MMMM Do YYYY, HH:mm:ss')}`); + console.log(`- Lockup period: ${currentLockup.lockUpPeriodSeconds} seconds`); + console.log(`- End time: ${moment.unix(currentLockup.startTime).add(parseInt(currentLockup.lockUpPeriodSeconds), 'seconds').format('MMMM Do YYYY, HH:mm:ss')}`); console.log(`- Release frequency: ${currentLockup.releaseFrequencySeconds} seconds`); + console.log(`- Investors: ${investors.length}`); + // ------------------ + + let options = [ + 'Modify properties', + 'Show investors', + 'Add investors to this lockup', + 'Remove investors from this lockup', + 'Delete this lockup type' + ]; + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Modify properties': + let lockupAmount = readlineSync.questionInt(`Enter the amount of tokens that will be locked: `); + let minuteFromNow = Math.floor(Date.now() / 1000) + 60; + let startTime = readlineSync.questionInt(`Enter the start time (Unix Epoch time) of the lockup type (a minute from now = ${minuteFromNow}): `, { defaultInput: minuteFromNow }); + let lockUpPeriodSeconds = readlineSync.questionInt(`Enter the total period (seconds) of the lockup type (ten minutes = 600): `, { defaultInput: 600 }); + let releaseFrequencySeconds = readlineSync.questionInt(`Enter how often to release a tranche of tokens in seconds (one minute = 60): `, { defaultInput: 60 }); + let modifyLockUpTypeAction = currentTransferManager.methods.modifyLockUpType(web3.utils.toWei(lockupAmount.toString()), startTime, lockUpPeriodSeconds, releaseFrequencySeconds, lockupName); + let modifyLockUpTypeReceipt = await common.sendTransaction(modifyLockUpTypeAction); + let modifyLockUpTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, modifyLockUpTypeReceipt.logs, 'ModifyLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(modifyLockUpTypeEvent._lockupName)} lockup type has been modified successfully!`)); + break; + case 'Show investors': + if (investors.length > 0) { + console.log("************ List of investors ************"); + investors.map(i => console.log(i)); + } else { + console.log(chalk.yellow("There are no investors yet")); + } + break; + case 'Add this lockup to investors': + let investorsToAdd = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { + limit: function (input) { + return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(","); + let addInvestorToLockupAction; + if (investorsToAdd.length === 1) { + addInvestorToLockupAction = currentTransferManager.methods.addLockUpByName(investorsToAdd[0], lockupName); + } else { + addInvestorToLockupAction = currentTransferManager.methods.addLockUpByNameMulti(investorsToAdd, investorsToAdd.map(i => lockupName)); + } + let addInvestorToLockupReceipt = await common.sendTransaction(addInvestorToLockupAction); + let addInvestorToLockupEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, addInvestorToLockupReceipt.logs, 'AddLockUpToUser'); + addInvestorToLockupEvents.map(e => console.log(chalk.green(`${e._userAddress} has been added to ${web3.utils.hexToUtf8(e._lockupName)} successfully!`))); + break; + case 'Remove this lockup from investors': + let investorsToRemove = readlineSync.question(`Enter the addresses of the investors separated by comma (i.e.addr1, addr2, addr3): `, { + limit: function (input) { + return (input !== '' && input.split(",").every(a => web3.utils.isAddress(a))); + }, + limitMessage: `All addresses must be valid` + }).split(","); + let removeLockupFromInvestorAction; + if (investorsToRemove.length === 1) { + removeLockupFromInvestorAction = currentTransferManager.methods.removeLockUpFromUser(investorsToRemove[0], lockupName); + } else { + removeLockupFromInvestorAction = currentTransferManager.methods.removeLockUpFromUserMulti(investorsToRemove, investorsToRemove.map(i => lockupName)); + } + let removeLockUpFromUserReceipt = await common.sendTransaction(removeLockupFromInvestorAction); + let removeLockUpFromUserEvents = common.getMultipleEventsFromLogs(currentTransferManager._jsonInterface, removeLockUpFromUserReceipt.logs, 'RemoveLockUpFromUser'); + removeLockUpFromUserEvents.map(e => console.log(chalk.green(`${e._userAddress} has been removed to ${web3.utils.hexToUtf8(e._lockupName)} successfully!`))); + break; + case 'Delete this lockup type': + let isEmpty = investors.length === 0; + if (!isEmpty) { + console.log(chalk.yellow(`This lockup have investors added to it. To delete it you must remove them first.`)); + if (readlineSync.keyInYNStrict(`Do you want to remove them? `)) { + let data = investors.map(i => [i, lockupName]) + let batches = common.splitIntoBatches(data, gbl.constants.DEFAULT_BATCH_SIZE); + let [investorArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove the following investors:\n\n`, investorArray[batch], '\n'); + let action = currentTransferManager.methods.removeLockUpFromUserMulti(investorArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove lockups from multiple investors transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } + isEmpty = true; + } + } + if (isEmpty) { + let removeLockupTypeAction = currentTransferManager.methods.removeLockupType(lockupName); + let removeLockupTypeReceipt = await common.sendTransaction(removeLockupTypeAction); + let removeLockupTypeEvent = common.getEventFromLogs(currentTransferManager._jsonInterface, removeLockupTypeReceipt.logs, 'RemoveLockUpType'); + console.log(chalk.green(`${web3.utils.hexToUtf8(removeLockupTypeEvent._lockupName)} lockup type has been deleted successfully!`)); + } + return; + case 'RETURN': + return; + } + + await manageExistingLockups(lockupName); +} + +async function operateWithMultipleLockups(currentLockups) { + let options = ['Add multiple lockups']; + if (currentLockups.length > 0) { + options.push('Modify multiple lockups'); + } + options.push( + 'Delete multiple lockups', + 'Add lockups to multiple investors', + 'Remove lockups from multiple investors' + ); + + let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); + let optionSelected = index !== -1 ? options[index] : 'RETURN'; + console.log('Selected:', optionSelected, '\n'); + switch (optionSelected) { + case 'Add multiple lockups': + await addLockupsInBatch(); + break; + case 'Modify multiple lockups': + await modifyLockupsInBatch(); + break; + case 'Delete multiple lockups': + await deleteLockupsInBatch(); + break; + case 'Add lockups to multiple investors': + await addLockupsToInvestorsInBatch(); + break; + case 'Remove lockups from multiple investors': + await removeLockupsFromInvestorsInBatch(); + break; + } +} + +async function addLockupsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_LOCKUP_DATA_CSV}): `, { + defaultInput: ADD_LOCKUP_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => !isNaN(row[0]) && + moment.unix(row[1]).isValid() && + (!isNaN(row[2] && (parseFloat(row[2]) % 1 === 0))) && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 0))) && + typeof row[4] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [amountArray, startTimeArray, lockUpPeriodArray, releaseFrequencyArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add the following lockups: \n\n`, lockupNameArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + amountArray[batch] = amountArray[batch].map(n => web3.utils.toWei(n.toString())); + let action = currentTransferManager.methods.addNewLockUpTypeMulti(amountArray[batch], startTimeArray[batch], lockUpPeriodArray[batch], releaseFrequencyArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add multiple lockups transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function modifyLockupsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${MODIFY_LOCKUP_DATA_CSV}): `, { + defaultInput: MODIFY_LOCKUP_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => !isNaN(row[0]) && + moment.unix(row[1]).isValid() && + (!isNaN(row[2] && (parseFloat(row[2]) % 1 === 0))) && + (!isNaN(row[3] && (parseFloat(row[3]) % 1 === 0))) && + typeof row[4] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [amountArray, startTimeArray, lockUpPeriodArray, releaseFrequencyArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to modify the following lockups: \n\n`, lockupNameArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + amountArray[batch] = amountArray[batch].map(n => web3.utils.toWei(n.toString())); + let action = currentTransferManager.methods.modifyLockUpTypeMulti(amountArray[batch], startTimeArray[batch], lockUpPeriodArray[batch], releaseFrequencyArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Modify multiple lockups transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function deleteLockupsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${DELETE_LOCKUP_DATA_CSV}): `, { + defaultInput: DELETE_LOCKUP_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter(row => typeof row[0] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to delete the following lockups: \n\n`, lockupNameArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.removeLockupTypeMulti(lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Delete multiple lockups transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function addLockupsToInvestorsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ADD_LOCKUP_INVESTOR_DATA_CSV}): `, { + defaultInput: ADD_LOCKUP_INVESTOR_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to add lockups to the following investors: \n\n`, investorArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.addLockUpByNameMulti(investorArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Add lockups to multiple investors transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +async function removeLockupsFromInvestorsInBatch() { + let csvFilePath = readlineSync.question(`Enter the path for csv data file (${REMOVE_LOCKUP_INVESTOR_DATA_CSV}): `, { + defaultInput: REMOVE_LOCKUP_INVESTOR_DATA_CSV + }); + let batchSize = readlineSync.question(`Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, { + limit: function (input) { + return parseInt(input) > 0; + }, + limitMessage: 'Must be greater than 0', + defaultInput: gbl.constants.DEFAULT_BATCH_SIZE + }); + let parsedData = csvParse(csvFilePath); + let validData = parsedData.filter( + row => web3.utils.isAddress(row[0]) && + typeof row[1] === 'string'); + let invalidRows = parsedData.filter(row => !validData.includes(row)); + if (invalidRows.length > 0) { + console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `)); + } + let batches = common.splitIntoBatches(validData, batchSize); + let [investorArray, lockupNameArray] = common.transposeBatches(batches); + for (let batch = 0; batch < batches.length; batch++) { + console.log(`Batch ${batch + 1} - Attempting to remove the following investors: \n\n`, investorArray[batch], '\n'); + lockupNameArray[batch] = lockupNameArray[batch].map(n => web3.utils.toHex(n)); + let action = currentTransferManager.methods.removeLockUpFromUserMulti(investorArray[batch], lockupNameArray[batch]); + let receipt = await common.sendTransaction(action); + console.log(chalk.green('Remove lockups from multiple investors transaction was successful.')); + console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`); + } +} + +/* +// Copied from tests +function signData(tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo, pk) { + let packedData = ethers.utils + .solidityKeccak256( + ["address", "address", "uint256", "uint256", "uint256", "bool", "uint256", "uint256"], + [tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo] + ) + .slice(2); + packedData = new Buffer(packedData, "hex"); + packedData = Buffer.concat([new Buffer(`\x19Ethereum Signed Message: \n${ packedData.length.toString() } `), packedData]); + packedData = web3.sha3(`0x${ packedData.toString("hex") } `, { encoding: "hex" }); + return ethUtil.ecsign(new Buffer(packedData.slice(2), "hex"), new Buffer(pk, "hex")); +} +*/ + +function toWeiPercentage(number) { + return web3.utils.toWei((parseFloat(number) / 100).toString()); +} + +function fromWeiPercentage(number) { + return web3.utils.fromWei(new web3.utils.BN(number).muln(100)).toString(); +} + +async function getAllModulesByType(type) { + function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused) { + this.name = _name; + this.type = _moduleType; + this.address = _address; + this.factoryAddress = _factoryAddress; + this.archived = _archived; + this.paused = _paused; + } + + let modules = []; + + let allModules = await securityToken.methods.getModulesByType(type).call(); + + for (let i = 0; i < allModules.length; i++) { + let details = await securityToken.methods.getModule(allModules[i]).call(); + let nameTemp = web3.utils.hexToUtf8(details[0]); + let pausedTemp = null; + if (type == gbl.constants.MODULES_TYPES.STO || type == gbl.constants.MODULES_TYPES.TRANSFER) { + let abiTemp = JSON.parse(require('fs').readFileSync(`${__dirname}/../../build/contracts/${nameTemp}.json`).toString()).abi; + let contractTemp = new web3.eth.Contract(abiTemp, details[1]); + pausedTemp = await contractTemp.methods.paused().call(); + } + modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp)); + } + + return modules; +} + +async function initialize(_tokenSymbol) { + welcome(); + await setup(); + if (typeof _tokenSymbol === 'undefined') { + tokenSymbol = await selectToken(); + } else { + tokenSymbol = _tokenSymbol; + } + let securityTokenAddress = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); + if (securityTokenAddress == '0x0000000000000000000000000000000000000000') { + console.log(chalk.red(`Selected Security Token ${tokenSymbol} does not exist.`)); + process.exit(0); + } + let securityTokenABI = abis.securityToken(); + securityToken = new web3.eth.Contract(securityTokenABI, securityTokenAddress); + securityToken.setProvider(web3.currentProvider); +} + +function welcome() { common.logAsciiBull(); console.log("*********************************************"); console.log("Welcome to the Command-Line Transfer Manager."); console.log("*********************************************"); console.log("Issuer Account: " + Issuer.address + "\n"); +} - await setup(); - try { - await start_explorer(); - } catch (err) { - console.log(err); - return; - } -}; - -async function setup(){ +async function setup() { try { let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); let securityTokenRegistryABI = abis.securityTokenRegistry(); securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); securityTokenRegistry.setProvider(web3.currentProvider); + + let moduleRegistryAddress = await contracts.moduleRegistry(); + let moduleRegistryABI = abis.moduleRegistry(); + moduleRegistry = new web3.eth.Contract(moduleRegistryABI, moduleRegistryAddress); + moduleRegistry.setProvider(web3.currentProvider); } catch (err) { console.log(err) - console.log('\x1b[31m%s\x1b[0m',"There was a problem getting the contracts. Make sure they are deployed to the selected network."); + console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); process.exit(0); } } -async function start_explorer() { - console.log('\n\x1b[34m%s\x1b[0m',"Transfer Manager - Main Menu"); +async function selectToken() { + let result = null; - if (!tokenSymbol) - tokenSymbol = readlineSync.question('Enter the token symbol: '); + let userTokens = await securityTokenRegistry.methods.getTokensByOwner(Issuer.address).call(); + let tokenDataArray = await Promise.all(userTokens.map(async function (t) { + let tokenData = await securityTokenRegistry.methods.getSecurityTokenData(t).call(); + return { symbol: tokenData[0], address: t }; + })); + let options = tokenDataArray.map(function (t) { + return `${t.symbol} - Deployed at ${t.address} `; + }); + options.push('Enter token symbol manually'); - let result = await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call(); - if (result == "0x0000000000000000000000000000000000000000") { - tokenSymbol = undefined; - console.log(chalk.red(`Token symbol provided is not a registered Security Token.`)); - } else { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI,result); + let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'EXIT' }); + let selected = index != -1 ? options[index] : 'EXIT'; + switch (selected) { + case 'Enter token symbol manually': + result = readlineSync.question('Enter the token symbol: '); + break; + case 'EXIT': + process.exit(); + break; + default: + result = tokenDataArray[index].symbol; + break; + } - let forcedTransferDisabled = await securityToken.methods.controllerDisabled().call(); + return result; +} - if (!forcedTransferDisabled) { - let options = ['Disable controller', 'Set controller']; - let controller = await securityToken.methods.controller().call(); - if (controller == Issuer.address) { - options.push('Force Transfer') - } - let index = readlineSync.keyInSelect(options, 'What do you want to do?'); - let optionSelected = options[index]; - console.log('Selected:', index != -1 ? optionSelected : 'Cancel', '\n'); - switch (optionSelected) { - case 'Disable controller': - if (readlineSync.keyInYNStrict()) { - let disableControllerAction = securityToken.methods.disableController(); - await common.sendTransaction(Issuer, disableControllerAction, defaultGasPrice); - console.log(chalk.green(`Forced transfers have been disabled permanently`)); - } - break; - case 'Set controller': - let controllerAddress = readlineSync.question(`Enter the address for the controller (${Issuer.address}): `, { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - defaultInput: Issuer.address - }); - let setControllerAction = securityToken.methods.setController(controllerAddress); - let setControllerReceipt = await common.sendTransaction(Issuer, setControllerAction, defaultGasPrice); - let setControllerEvent = common.getEventFromLogs(securityToken._jsonInterface, setControllerReceipt.logs, 'SetController'); - console.log(chalk.green(`New controller is ${setControllerEvent._newController}`)); - break; - case 'Force Transfer': - let from = readlineSync.question('Enter the address from which to take tokens: ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(from).call()); - console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol}`)); - let to = readlineSync.question('Enter address where to send tokens: ', { - limit: function(input) { - return web3.utils.isAddress(input); - }, - limitMessage: "Must be a valid address", - }); - let toBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(to).call()); - console.log(chalk.yellow(`Balance of ${to}: ${toBalance} ${tokenSymbol}`)); - let amount = readlineSync.question('Enter amount of tokens to transfer: ', { - limit: function(input) { - return parseInt(input) <= parseInt(fromBalance); - }, - limitMessage: `Amount must be less or equal than ${fromBalance} ${tokenSymbol}`, - }); - let data = readlineSync.question('Enter the data to indicate validation: '); - let log = readlineSync.question('Enter the data attached to the transfer by controller to emit in event: '); - let forceTransferAction = securityToken.methods.forceTransfer(from, to, web3.utils.toWei(amount), web3.utils.asciiToHex(data), web3.utils.asciiToHex(log)); - let forceTransferReceipt = await common.sendTransaction(Issuer, forceTransferAction, defaultGasPrice, 0, 1.5); - let forceTransferEvent = common.getEventFromLogs(securityToken._jsonInterface, forceTransferReceipt.logs, 'ForceTransfer'); - console.log(chalk.green(` ${forceTransferEvent._controller} has successfully forced a transfer of ${web3.utils.fromWei(forceTransferEvent._value)} ${tokenSymbol} - from ${forceTransferEvent._from} to ${forceTransferEvent._to} - Verified transfer: ${forceTransferEvent._verifyTransfer} - Data: ${web3.utils.hexToAscii(forceTransferEvent._data)} - `)); - console.log(`Balance of ${from} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(from).call())} ${tokenSymbol}`); - console.log(`Balance of ${to} after transfer: ${web3.utils.fromWei(await securityToken.methods.balanceOf(to).call())} ${tokenSymbol}`); - break; - default: - process.exit(0); - } - } else { - console.log(chalk.red(`Controller featueres are permanently disabled for this token.`)) - tokenSymbol = undefined; - } - } +async function logTotalInvestors() { + let investorsCount = await securityToken.methods.getInvestorCount().call(); + console.log(chalk.yellow(`Total investors at the moment: ${investorsCount} `)); +} - //Restart - await start_explorer(); +async function logBalance(from, totalSupply) { + let fromBalance = web3.utils.fromWei(await securityToken.methods.balanceOf(from).call()); + let percentage = totalSupply != '0' ? ` - ${parseFloat(fromBalance) / parseFloat(totalSupply) * 100}% of total supply` : ''; + console.log(chalk.yellow(`Balance of ${from}: ${fromBalance} ${tokenSymbol} ${percentage} `)); } module.exports = { - executeApp: async function(type, remoteNetwork) { - return executeApp(type, remoteNetwork); + executeApp: async function (_tokenSymbol) { + await initialize(_tokenSymbol); + return executeApp(); + }, + addTransferManagerModule: async function (_tokenSymbol) { + await initialize(_tokenSymbol); + return addTransferManagerModule(); + }, + modifyWhitelistInBatch: async function (_tokenSymbol, _csvFilePath, _batchSize) { + await initialize(_tokenSymbol); + let gmtModules = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); + let generalTransferManagerAddress = gmtModules[0]; + currentTransferManager = new web3.eth.Contract(abis.generalTransferManager(), generalTransferManagerAddress); + currentTransferManager.setProvider(web3.currentProvider); + return modifyWhitelistInBatch(_csvFilePath, _batchSize); } -} \ No newline at end of file +} diff --git a/CLI/commands/transfer_ownership.js b/CLI/commands/transfer_ownership.js index 68ca7eb7a..99e12b80f 100644 --- a/CLI/commands/transfer_ownership.js +++ b/CLI/commands/transfer_ownership.js @@ -1,6 +1,5 @@ var chalk = require('chalk'); var common = require('./common/common_functions'); -var global = require('./common/global'); /////////////////////////////ARTIFACTS////////////////////////////////////////// var abis = require('./helpers/contract_abis') @@ -8,8 +7,7 @@ var abis = require('./helpers/contract_abis') let contract; //////////////////////////////////////////ENTRY INTO SCRIPT////////////////////////////////////////// -async function startScript(contractAddress, transferTo, remoteNetwork) { - await global.initialize(remoteNetwork); +async function startScript(contractAddress, transferTo) { if (!web3.utils.isAddress(contractAddress) || !web3.utils.isAddress(transferTo)) { console.log(chlak.red(`Please enter valid addresses`)); @@ -28,14 +26,14 @@ async function transferOwnership(transferTo) { console.log(chalk.red(`You are not the current owner ot this contract. Current owner is ${currentOwner}.`)); } else { let transferOwnershipAction = contract.methods.transferOwnership(transferTo); - let receipt = await common.sendTransaction(Issuer, transferOwnershipAction, defaultGasPrice); + let receipt = await common.sendTransaction(transferOwnershipAction); let event = common.getEventFromLogs(contract._jsonInterface, receipt.logs, 'OwnershipTransferred'); console.log(chalk.green(`Ownership transferred successfully. New owner is ${event.newOwner}`)); } }; module.exports = { - executeApp: async function(contractAddress, transferTo, remoteNetwork) { - return startScript(contractAddress, transferTo, remoteNetwork); + executeApp: async function(contractAddress, transferTo) { + return startScript(contractAddress, transferTo); } } diff --git a/CLI/commands/whitelist.js b/CLI/commands/whitelist.js deleted file mode 100644 index 4750adffa..000000000 --- a/CLI/commands/whitelist.js +++ /dev/null @@ -1,302 +0,0 @@ -var fs = require('fs'); -var csv = require('fast-csv'); -var BigNumber = require('bignumber.js'); -var common = require('./common/common_functions'); -var global = require('./common/global'); - -/////////////////////////////ARTIFACTS////////////////////////////////////////// -var contracts = require('./helpers/contract_addresses'); -var abis = require('./helpers/contract_abis'); - -////////////////////////////USER INPUTS////////////////////////////////////////// -let tokenSymbol = process.argv.slice(2)[0]; //token symbol -let BATCH_SIZE = process.argv.slice(2)[1]; //batch size -if (!BATCH_SIZE) BATCH_SIZE = 70; -let remoteNetwork = process.argv.slice(2)[2]; - -/////////////////////////GLOBAL VARS////////////////////////////////////////// - -//distribData is an array of batches. i.e. if there are 200 entries, with batch sizes of 75, we get [[75],[75],[50]] -let distribData = new Array(); -//allocData is a temporary array that stores up to the batch size, -//then gets push into distribData, then gets set to 0 to start another batch -let allocData = new Array(); -//full file data is a single array that contains all arrays. i.e. if there are 200 entries we get [[200]] -let fullFileData = new Array(); -let badData = new Array(); - -const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); - -//////////////////////////////////////////ENTRY INTO SCRIPT////////////////////////////////////////// - -startScript(); - -async function startScript() { - if (remoteNetwork == 'undefined') remoteNetwork = undefined; - await global.initialize(remoteNetwork); - - try { - let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); - let securityTokenRegistryABI = abis.securityTokenRegistry(); - securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); - securityTokenRegistry.setProvider(web3.currentProvider); - - console.log("Processing investor CSV upload. Batch size is "+BATCH_SIZE+" accounts per transaction"); - readFile(); - } catch (err) { - console.log(err) - console.log('\x1b[31m%s\x1b[0m', "There was a problem getting the contracts. Make sure they are deployed to the selected network."); - return; - } -} - -///////////////////////////FUNCTION READING THE CSV FILE -function readFile() { - var stream = fs.createReadStream("./CLI/data/whitelist_data.csv"); - - let index = 0; - let batch = 0; - console.log(` - -------------------------------------------- - ----------- Parsing the csv file ----------- - -------------------------------------------- - `); - - var csvStream = csv() - .on("data", function (data) { - // console.log(data[1]) - // console.log(data[2]) - // console.log(data[3]) - let isAddress = web3.utils.isAddress(data[0]); - let sellValid = isValidDate(data[1]) - let buyValid = isValidDate(data[2]) - let kycExpiryDate = isValidDate(data[3]) - let canBuyFromSTO = (typeof JSON.parse(data[4].toLowerCase())) == "boolean" ? JSON.parse(data[4].toLowerCase()) : "not-valid"; - - if (isAddress && sellValid && buyValid && kycExpiryDate && (canBuyFromSTO != "not-valid") ) { - let userArray = new Array() - let checksummedAddress = web3.utils.toChecksumAddress(data[0]); - - userArray.push(checksummedAddress) - userArray.push(sellValid) - userArray.push(buyValid) - userArray.push(kycExpiryDate) - userArray.push(canBuyFromSTO) - // console.log(userArray) - allocData.push(userArray); - fullFileData.push(userArray); - index++; - if (index >= BATCH_SIZE) { - distribData.push(allocData); - // console.log("DIS", distribData); - allocData = []; - // console.log("ALLOC", allocData); - index = 0; - } - - } else { - let userArray = new Array() - //dont need this here, as if it is NOT an address this function will fail - //let checksummedAddress = web3.utils.toChecksumAddress(data[1]); - userArray.push(data[0]) - userArray.push(sellValid) - userArray.push(buyValid) - userArray.push(kycExpiryDate); - userArray.push(canBuyFromSTO); - badData.push(userArray); - fullFileData.push(userArray) - } - }) - .on("end", function () { - //Add last remainder batch - distribData.push(allocData); - allocData = []; - - setInvestors(); - }); - - stream.pipe(csvStream); -} - -////////////////////////MAIN FUNCTION COMMUNICATING TO BLOCKCHAIN -async function setInvestors() { - let tokenDeployed = false; - let tokenDeployedAddress; - // Let's check if token has already been deployed, if it has, skip to STO - await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call({}, function (error, result) { - if (result != "0x0000000000000000000000000000000000000000") { - console.log('\x1b[32m%s\x1b[0m', "Token deployed at address " + result + "."); - tokenDeployedAddress = result; - tokenDeployed = true; - } - }); - if (tokenDeployed) { - let securityTokenABI = abis.securityToken(); - securityToken = new web3.eth.Contract(securityTokenABI, tokenDeployedAddress); - } - let gmtModules = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralTransferManager')).call(); - let generalTransferManagerAddress = gmtModules[0]; - let generalTransferManagerABI = abis.generalTransferManager(); - let generalTransferManager = new web3.eth.Contract(generalTransferManagerABI, generalTransferManagerAddress); - - console.log(` - ------------------------------------------------------- - ----- Sending buy/sell restrictions to blockchain ----- - ------------------------------------------------------- - `); - - //this for loop will do the batches, so it should run 75, 75, 50 with 200 - for (let i = 0; i < distribData.length; i++) { - try { - let investorArray = []; - let fromTimesArray = []; - let toTimesArray = []; - let expiryTimeArray = []; - let canBuyFromSTOArray = []; - - //splitting the user arrays to be organized by input - for (let j = 0; j < distribData[i].length; j++) { - investorArray.push(distribData[i][j][0]) - fromTimesArray.push(distribData[i][j][1]) - toTimesArray.push(distribData[i][j][2]) - expiryTimeArray.push(distribData[i][j][3]) - canBuyFromSTOArray.push(distribData[i][j][4]) - } - - //fromTimes is ability to sell coin FROM your account (2nd row in csv, 2nd parameter in modifyWhiteList() ) - //toTimes is ability to buy coins TOwards your account (3rd row in csv, 3rd parameter in modifyWhiteList() ) - //expiryTime is time at which KYC of investor get expired (4th row in csv, 4rd parameter in modifyWhiteList() ) - let modifyWhitelistMultiAction = generalTransferManager.methods.modifyWhitelistMulti(investorArray, fromTimesArray, toTimesArray, expiryTimeArray, canBuyFromSTOArray); - let r = await common.sendTransaction(Issuer, modifyWhitelistMultiAction, defaultGasPrice); - console.log(`Batch ${i} - Attempting to modifyWhitelist accounts:\n\n`, investorArray, "\n\n"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------"); - console.log("Whitelist transaxction was successful.", r.gasUsed, "gas used. Spent:", web3.utils.fromWei(BigNumber(r.gasUsed * defaultGasPrice).toString(), "ether"), "Ether"); - console.log("---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n\n"); - - } catch (err) { - console.log("ERROR:", err); - } - } - - console.log("Retrieving logs to determine investors have had their times uploaded correctly.\n\n") - - let totalInvestors = 0; - let updatedInvestors = 0; - - let investorData_Events = new Array(); - let investorObjectLookup = {}; - - let event_data = await generalTransferManager.getPastEvents('ModifyWhitelist', { - fromBlock: 0, - toBlock: 'latest' - }, function (error, events) { - //console.log(error); - }); - - for (var i = 0; i < event_data.length; i++) { - let combineArray = []; - - let investorAddress_Event = event_data[i].returnValues._investor; - let fromTime_Event = event_data[i].returnValues._fromTime - let toTime_Event = event_data[i].returnValues._toTime - let expiryTime_Event = event_data[i].returnValues._expiryTime - let canBuyFromSTO_Event = event_data[i].returnValues._canBuyFromSTO - let blockNumber = event_data[i].blockNumber - - combineArray.push(investorAddress_Event); - combineArray.push(fromTime_Event); - combineArray.push(toTime_Event); - combineArray.push(expiryTime_Event); - combineArray.push(canBuyFromSTO_Event); - combineArray.push(blockNumber) - - investorData_Events.push(combineArray) - - //we have already recorded it, so this is an update to our object - if (investorObjectLookup.hasOwnProperty(investorAddress_Event)) { - - //the block number form the event we are checking is bigger, so we gotta replace it - if (investorObjectLookup[investorAddress_Event].recordedBlockNumber < blockNumber) { - investorObjectLookup[investorAddress_Event] = { fromTime: fromTime_Event, toTime: toTime_Event, expiryTime: expiryTime_Event, canBuyFromSTO: canBuyFromSTO_Event, recordedBlockNumber: blockNumber }; - updatedInvestors += 1; - // investorAddress_Events.push(investorAddress_Event); not needed, because we start the obj with zero events - - } else { - //do nothing. so if we find an event, and it was an older block, its old, we dont care - } - //we have never recorded this address as an object key, so we need to add it to our list of investors updated by the csv - } else { - investorObjectLookup[investorAddress_Event] = { fromTime: fromTime_Event, toTime: toTime_Event, expiryTime: expiryTime_Event, canBuyFromSTO: canBuyFromSTO_Event, recordedBlockNumber: blockNumber }; - totalInvestors += 1; - // investorAddress_Events.push(investorAddress_Event); - } - } - let investorAddress_Events = Object.keys(investorObjectLookup) - - console.log(`******************** EVENT LOGS ANALYSIS COMPLETE ********************\n`); - console.log(`A total of ${totalInvestors} investors have been whitelisted total, all time.\n`); - console.log(`This script in total sent ${fullFileData.length - badData.length} new investors and updated investors to the blockchain.\n`); - console.log(`There were ${badData.length} bad entries that didnt get sent to the blockchain in the script.\n`); - - // console.log("LIST OF ALL INVESTOR DATA FROM EVENTS:", investorData_Events) - // console.log(fullFileData) - console.log("************************************************************************************************"); - console.log("OBJECT WITH EVERY USER AND THEIR UPDATED TIMES: \n\n", investorObjectLookup) - console.log("************************************************************************************************"); - console.log("LIST OF ALL INVESTORS WHITELISTED: \n\n", investorAddress_Events) - - let missingDistribs = []; - for (let l = 0; l < fullFileData.length; l++) { - if (!investorObjectLookup.hasOwnProperty(fullFileData[l][0])) { - missingDistribs.push(fullFileData[l]) - } - } - - if (missingDistribs.length > 0) { - console.log("************************************************************************************************"); - console.log("-- No LogModifyWhitelist event was found for the following data arrays. Please review them manually --") - console.log(missingDistribs) - // for (var i = 0; i < missingDistribs.length; i++) { - // console.log('\x1b[31m%s\x1b[0m', `No Transfer event was found for account ${missingDistribs[i]}`); - // } - console.log("************************************************************************************************"); - } else { - console.log("\n************************************************************************************************"); - console.log("All accounts passed through from the CSV were successfully whitelisted, because we were able to read them all from events") - console.log("************************************************************************************************"); - } - // console.log(`Run 'node scripts/verify_airdrop.js ${polyDistribution.address} > scripts/data/review.csv' to get a log of all the accounts that were distributed the airdrop tokens.`) -} - -//will be deleted once DATES are updated -function isValidDayInput(days) { - let today = Date.now() / 1000 - let isValid = !isNaN(days) - if (isValid) { - let addedSeconds = days * 86400 - - let unixTimestamp = today + addedSeconds - console.log("unxitimestapm :" , (unixTimestamp)) - - return unixTimestamp - } else { - return false - } -} - -function isValidDate(date) { - var matches = /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})$/.exec(date); - if (matches == null) return false; - var d = matches[2]; - var m = matches[1] - 1; //not clear why this is -1, but it works after checking - var y = matches[3]; - var composedDate = new Date(y, m, d); - var timestampDate = composedDate.getTime() - - //note, some reason these timestamps are being recorded +4 hours UTC - if (composedDate.getDate() == d && composedDate.getMonth() == m && composedDate.getFullYear() == y) { - return timestampDate / 1000 - } else { - return false - } -} diff --git a/CLI/data/dividendsExclusions_data.csv b/CLI/data/Checkpoint/exclusions_data.csv similarity index 100% rename from CLI/data/dividendsExclusions_data.csv rename to CLI/data/Checkpoint/exclusions_data.csv diff --git a/CLI/data/Checkpoint/tax_withholding_data.csv b/CLI/data/Checkpoint/tax_withholding_data.csv new file mode 100644 index 000000000..10c2928c8 --- /dev/null +++ b/CLI/data/Checkpoint/tax_withholding_data.csv @@ -0,0 +1,10 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,0.5 +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,1 +0xac297053173b02b02a737d47f7b4a718e5b170ef,2 +0x49fc0b78238dab644698a90fa351b4c749e123d2,10 +0x10223927009b8add0960359dd90d1449415b7ca9,15 +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,50 +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,0 +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,23 +0x56be93088141b16ebaa9416122fd1d928da25ecf,45 +0xbb276b6f68f0a41d54b7e0a608fe8eb1ebdee7b0,67 \ No newline at end of file diff --git a/CLI/data/multi_mint_data.csv b/CLI/data/ST/multi_mint_data.csv similarity index 69% rename from CLI/data/multi_mint_data.csv rename to CLI/data/ST/multi_mint_data.csv index 0b157b164..8b27f06b0 100644 --- a/CLI/data/multi_mint_data.csv +++ b/CLI/data/ST/multi_mint_data.csv @@ -1,8 +1,8 @@ -0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000 +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,123.4 0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,1000 -0xac297053173b02b02a737d47f7b4a718e5b170ef,1000 +0xac297053173b02b02a737d47f7b4a718e5b170ef,234.5 0x49fc0b78238dab644698a90fa351b4c749e123d2,1000 -0x10223927009b8add0960359dd90d1449415b7ca9,1000 +0x10223927009b8add0960359dd90d1449415b7ca9,345.6 0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,1000 0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1000 0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,1000 diff --git a/CLI/data/accredited_data.csv b/CLI/data/STO/USDTieredSTO/accredited_data.csv similarity index 100% rename from CLI/data/accredited_data.csv rename to CLI/data/STO/USDTieredSTO/accredited_data.csv diff --git a/CLI/data/nonAccreditedLimits_data.csv b/CLI/data/STO/USDTieredSTO/nonAccreditedLimits_data.csv similarity index 100% rename from CLI/data/nonAccreditedLimits_data.csv rename to CLI/data/STO/USDTieredSTO/nonAccreditedLimits_data.csv diff --git a/CLI/data/STO/capped_sto_data.yml b/CLI/data/STO/capped_sto_data.yml new file mode 100644 index 000000000..41653c9ff --- /dev/null +++ b/CLI/data/STO/capped_sto_data.yml @@ -0,0 +1,14 @@ +# type -> 'CappedSTO' for cappedSTO, 'USDTieredSTO' for USDTieredSTO +type: 'CappedSTO' +# cap -> Maximum No. of tokens for sale. +cap: 1000000 +# startTime -> Unix timestamp at which offering get started. '' for CLI default (one minute from now) +startTime: '' +# endTime -> Unix timestamp at which offering get ended. '' for CLI default (one month from now) +endTime: '' +# wallet -> Ethereum account address to hold the funds +wallet: '0x0a519b4b6501f92e8f516230b97aca83257b0c01' +# raiseType -> Type of currency used to collect the funds. [0] for ETH, [1] for POLY +raiseType: [0] +# rate -> Token units a buyer gets per unit of selected raise type. +rate: 1000 \ No newline at end of file diff --git a/CLI/data/STO/usd_tiered_sto_data.yml b/CLI/data/STO/usd_tiered_sto_data.yml new file mode 100644 index 000000000..29655c5bf --- /dev/null +++ b/CLI/data/STO/usd_tiered_sto_data.yml @@ -0,0 +1,37 @@ +# type -> 'CappedSTO' for cappedSTO, 'USDTieredSTO' for USDTieredSTO +type: 'USDTieredSTO' + +funding: + # fundigType -> Types of currency used to collect the funds, 0 for ETH, 1 for POLY, 2 for DAI, any combination of them for more than one (i.e. [0, 1, 2] for all of them) + raiseType: [0, 1] + +addresses: + # wallet -> Ethereum account address to hold the funds. + wallet: '0x0a519b4b6501f92e8f516230b97aca83257b0c01' + # reserveWallet -> Ethereum account address to receive unsold tokens. + reserveWallet: '0x0a519b4b6501f92e8f516230b97aca83257b0c01' + # usdToken -> Contract address of the stable coin. + usdToken: '0x0000000000000000000000000000000000000000' + + +tiers: + # tokensPerTiers -> Total tokens for each tier + tokensPerTier: !!seq [ 190000000, 100000000, 200000000 ] + # ratePerTiers -> Rate for each tier (in USD) + ratePerTier: !!seq [ 0.05, 0.10, 0.15 ] + # discountedTokensPerTiers -> Tokens for discounted rate for POLY investments. 0 for no discounted tokens + tokensPerTierDiscountPoly: !!seq [ 0, 0, 100000000 ] + # discountedRatePerTiers -> Discounted rate for POLY investments for each tier. 0 for no discounted rate + ratePerTierDiscountPoly: !!seq [ 0, 0, 0.075 ] + +limits: + # minimumInvestmentUSD -> Minimun investment in USD. + minimumInvestmentUSD: 5 + # nonAccreditedLimitUSD -> Limit in USD for non-accredited investors + nonAccreditedLimitUSD: 10000 + +times: + # startTime -> Unix timestamp at which offering get started. '' for CLI default (one minute from now) + startTime: '' + # endTime -> Unix timestamp at which offering get ended. '' for CLI default (one month from now) + endTime: '' \ No newline at end of file diff --git a/CLI/data/ticker_data.csv b/CLI/data/Ticker/ticker_data.csv similarity index 100% rename from CLI/data/ticker_data.csv rename to CLI/data/Ticker/ticker_data.csv diff --git a/CLI/data/Transfer/BlacklistTM/add_blacklist_data.csv b/CLI/data/Transfer/BlacklistTM/add_blacklist_data.csv new file mode 100644 index 000000000..1d925053d --- /dev/null +++ b/CLI/data/Transfer/BlacklistTM/add_blacklist_data.csv @@ -0,0 +1,4 @@ +1559401200,1560178800,"FirstTenDays",30 +1560535200,1560621600,"NoRepeat",0 +1566734400,1567252800,"Every90",90 +1567296000,1567303200,"TwoHours",2 diff --git a/CLI/data/Transfer/BlacklistTM/add_investor_blacklist_data.csv b/CLI/data/Transfer/BlacklistTM/add_investor_blacklist_data.csv new file mode 100644 index 000000000..cd0fe922c --- /dev/null +++ b/CLI/data/Transfer/BlacklistTM/add_investor_blacklist_data.csv @@ -0,0 +1,12 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,"FirstTenDays" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,"FirstTenDays" +0xac297053173b02b02a737d47f7b4a718e5b170ef,"FirstTenDays" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"FirstTenDays" +0x10223927009b8add0960359dd90d1449415b7ca9,"FirstTenDays" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"NoRepeat" +0x10223927009b8add0960359dd90d1449415b7ca9,"NoRepeat" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,"NoRepeat" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,"NoRepeat" +0x10223927009b8add0960359dd90d1449415b7ca9,"Every90" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,"Every90" +0x56be93088141b16ebaa9416122fd1d928da25ecf,"Every90" \ No newline at end of file diff --git a/CLI/data/Transfer/BlacklistTM/delete_blacklist_data.csv b/CLI/data/Transfer/BlacklistTM/delete_blacklist_data.csv new file mode 100644 index 000000000..752cb89c1 --- /dev/null +++ b/CLI/data/Transfer/BlacklistTM/delete_blacklist_data.csv @@ -0,0 +1,4 @@ +"FirstTenDays" +"Every90" +"NoRepeat" +"TwoHours" \ No newline at end of file diff --git a/CLI/data/Transfer/BlacklistTM/modify_blacklist_data.csv b/CLI/data/Transfer/BlacklistTM/modify_blacklist_data.csv new file mode 100644 index 000000000..4b5e45b4d --- /dev/null +++ b/CLI/data/Transfer/BlacklistTM/modify_blacklist_data.csv @@ -0,0 +1,4 @@ +1559412000,1560178800,"FirstTenDays",30 +1560535200,1561032000,"NoRepeat",0 +1566734400,1567252800,"Every90",90 +1567296000,1567303200,"TwoHours",3 diff --git a/CLI/data/Transfer/BlacklistTM/remove_investor_blacklist_data.csv b/CLI/data/Transfer/BlacklistTM/remove_investor_blacklist_data.csv new file mode 100644 index 000000000..cd0fe922c --- /dev/null +++ b/CLI/data/Transfer/BlacklistTM/remove_investor_blacklist_data.csv @@ -0,0 +1,12 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,"FirstTenDays" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,"FirstTenDays" +0xac297053173b02b02a737d47f7b4a718e5b170ef,"FirstTenDays" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"FirstTenDays" +0x10223927009b8add0960359dd90d1449415b7ca9,"FirstTenDays" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"NoRepeat" +0x10223927009b8add0960359dd90d1449415b7ca9,"NoRepeat" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,"NoRepeat" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,"NoRepeat" +0x10223927009b8add0960359dd90d1449415b7ca9,"Every90" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,"Every90" +0x56be93088141b16ebaa9416122fd1d928da25ecf,"Every90" \ No newline at end of file diff --git a/CLI/data/whitelist_data.csv b/CLI/data/Transfer/GTM/whitelist_data.csv similarity index 97% rename from CLI/data/whitelist_data.csv rename to CLI/data/Transfer/GTM/whitelist_data.csv index 3276dec64..236cac436 100644 --- a/CLI/data/whitelist_data.csv +++ b/CLI/data/Transfer/GTM/whitelist_data.csv @@ -7,4 +7,4 @@ 0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,5/5/2018,1/8/2018,10/10/2019,false 0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5/5/2018,1/8/2018,10/10/2019,false 0x56be93088141b16ebaa9416122fd1d928da25ecf,5/5/2018,1/8/2018,10/10/2019,false -0xbb276b6f68f0a41d54b7e0a608fe8eb1ebdee7b0,5/5/2018,1/8/2018,10/10/2019,true +0xbb276b6f68f0a41d54b7e0a608fe8eb1ebdee7b0,5/5/2018,1/8/2018,10/10/2019,true \ No newline at end of file diff --git a/CLI/data/Transfer/LockupTM/add_lockup_data.csv b/CLI/data/Transfer/LockupTM/add_lockup_data.csv new file mode 100644 index 000000000..f3d27ab2d --- /dev/null +++ b/CLI/data/Transfer/LockupTM/add_lockup_data.csv @@ -0,0 +1,4 @@ +1000,1560178800,600,1,"TenMinutes" +1000,1560621600,3600,60,"OneHour" +2000,1567252800,7200,3600,"TwoHours" +3000,1567303200,14400,4800,"4Hours" diff --git a/CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv b/CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv new file mode 100644 index 000000000..68c08a7d7 --- /dev/null +++ b/CLI/data/Transfer/LockupTM/add_lockup_investor_data.csv @@ -0,0 +1,12 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,"TenMinutes" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,"TenMinutes" +0xac297053173b02b02a737d47f7b4a718e5b170ef,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"TenMinutes" +0x10223927009b8add0960359dd90d1449415b7ca9,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,"OneHour" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,"OneHour" +0x56be93088141b16ebaa9416122fd1d928da25ecf,"OneHour" \ No newline at end of file diff --git a/CLI/data/Transfer/LockupTM/delete_lockup_data.csv b/CLI/data/Transfer/LockupTM/delete_lockup_data.csv new file mode 100644 index 000000000..0d9203ffe --- /dev/null +++ b/CLI/data/Transfer/LockupTM/delete_lockup_data.csv @@ -0,0 +1,2 @@ +"TwoHours" +"4Hours" \ No newline at end of file diff --git a/CLI/data/Transfer/LockupTM/modify_lockup_data.csv b/CLI/data/Transfer/LockupTM/modify_lockup_data.csv new file mode 100644 index 000000000..2b520c4ff --- /dev/null +++ b/CLI/data/Transfer/LockupTM/modify_lockup_data.csv @@ -0,0 +1,4 @@ +1000,1560178800,600,10,"TenMinutes" +1000,1560623200,3600,60,"OneHour" +2000,1567252800,7200,3600,"TwoHours" +6000,1567303200,14400,4800,"4Hours" diff --git a/CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv b/CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv new file mode 100644 index 000000000..68c08a7d7 --- /dev/null +++ b/CLI/data/Transfer/LockupTM/remove_lockup_investor_data.csv @@ -0,0 +1,12 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,"TenMinutes" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,"TenMinutes" +0xac297053173b02b02a737d47f7b4a718e5b170ef,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"TenMinutes" +0x10223927009b8add0960359dd90d1449415b7ca9,"TenMinutes" +0x49fc0b78238dab644698a90fa351b4c749e123d2,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,"OneHour" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,"OneHour" +0x10223927009b8add0960359dd90d1449415b7ca9,"OneHour" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,"OneHour" +0x56be93088141b16ebaa9416122fd1d928da25ecf,"OneHour" \ No newline at end of file diff --git a/CLI/data/Transfer/MATM/add_manualapproval_data.csv b/CLI/data/Transfer/MATM/add_manualapproval_data.csv new file mode 100644 index 000000000..48d5f2a54 --- /dev/null +++ b/CLI/data/Transfer/MATM/add_manualapproval_data.csv @@ -0,0 +1,5 @@ +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x1DDF4FDBB8eaDB5dD6EeC6edB1A91a0926940a33,10,12/12/2019,Lorem ipsum dolor sit amet +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0xB849AC17d881183800300A31f022d3fe4B82D457,20,12/12/2019,Consectetur adipiscing elit +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0xD122e1951cfb337D5CC8bc5aDECC0eb66ffb4B80,25,12/12/2019,Pellentesque ultrices eros +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x1D9127821A244b64852E8Da431bb389B81710985,20,12/12/2019,Non eleifend ante tincidunt eget +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x42dc79375E511Fb7Ee17fF50417141bAE5E5698E,10,12/12/2019,Sed ante arcu \ No newline at end of file diff --git a/CLI/data/Transfer/MATM/modify_manualapproval_data.csv b/CLI/data/Transfer/MATM/modify_manualapproval_data.csv new file mode 100644 index 000000000..7e695f3b2 --- /dev/null +++ b/CLI/data/Transfer/MATM/modify_manualapproval_data.csv @@ -0,0 +1,5 @@ +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x1DDF4FDBB8eaDB5dD6EeC6edB1A91a0926940a33,12/12/2019,1,Lorem ipsum dolor sit amet,0 +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0xB849AC17d881183800300A31f022d3fe4B82D457,12/12/2019,2,Consectetur adipiscing elit,0 +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0xD122e1951cfb337D5CC8bc5aDECC0eb66ffb4B80,12/12/2019,3,Pellentesque ultrices eros,1 +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x1D9127821A244b64852E8Da431bb389B81710985,12/12/2019,4,Non eleifend ante tincidunt eget,2 +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x42dc79375E511Fb7Ee17fF50417141bAE5E5698E,12/12/2019,5,Sed ante arcu,1 \ No newline at end of file diff --git a/CLI/data/Transfer/MATM/revoke_manualapproval_data.csv b/CLI/data/Transfer/MATM/revoke_manualapproval_data.csv new file mode 100644 index 000000000..b398caf02 --- /dev/null +++ b/CLI/data/Transfer/MATM/revoke_manualapproval_data.csv @@ -0,0 +1,2 @@ +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0x1DDF4FDBB8eaDB5dD6EeC6edB1A91a0926940a33 +0x127b4F58A825Dfc6d4058AcdB7bB397d1F0411b5,0xB849AC17d881183800300A31f022d3fe4B82D457 \ No newline at end of file diff --git a/CLI/data/Transfer/PercentageTM/whitelist_data.csv b/CLI/data/Transfer/PercentageTM/whitelist_data.csv new file mode 100644 index 000000000..19d7f163c --- /dev/null +++ b/CLI/data/Transfer/PercentageTM/whitelist_data.csv @@ -0,0 +1,10 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,true +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,true +0xac297053173b02b02a737d47f7b4a718e5b170ef,true +0x49fc0b78238dab644698a90fa351b4c749e123d2,true +0x10223927009b8add0960359dd90d1449415b7ca9,true +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,true +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,true +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,false +0x56be93088141b16ebaa9416122fd1d928da25ecf,false +0xbb276b6f68f0a41d54b7e0a608fe8eb1ebdee7b0,false \ No newline at end of file diff --git a/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv b/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv new file mode 100644 index 000000000..1807e90e6 --- /dev/null +++ b/CLI/data/Transfer/VRTM/add_custom_restriction_data.csv @@ -0,0 +1,8 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,8/1/2019,90,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,30,10/12/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,15,10/1/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/5/2019,90,10/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,0.25,8/3/2019,30,10/15/2019,"Percentage" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,8/10/2019,15,10/10/2019,"Percentage" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,8/20/2019,10,10/22/2019,"Fixed" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,8/1/2019,2,10/10/2019,"Fixed" \ No newline at end of file diff --git a/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv b/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv new file mode 100644 index 000000000..1486579b7 --- /dev/null +++ b/CLI/data/Transfer/VRTM/add_daily_restriction_data.csv @@ -0,0 +1,8 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,1000,8/1/2019,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,10/12/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,10/10/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/3/2019,10/1/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,0.25,8/1/2019,10/10/2019,"Percentage" +0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,0.1,8/1/2019,10/5/2019,"Percentage" +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,1234,8/12/2019,10/10/2019,"Fixed" +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,5678,8/1/2019,10/10/2019,"Fixed" \ No newline at end of file diff --git a/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv b/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv new file mode 100644 index 000000000..b69f43361 --- /dev/null +++ b/CLI/data/Transfer/VRTM/modify_custom_restriction_data.csv @@ -0,0 +1,5 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,8/1/2019,90,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/2/2019,30,10/10/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,20,10/10/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/1/2019,90,20/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,25,8/1/2019,30,10/10/2019,"Fixed" \ No newline at end of file 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..908c164e9 --- /dev/null +++ b/CLI/data/Transfer/VRTM/modify_daily_restriction_data.csv @@ -0,0 +1,5 @@ +0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,2000,8/1/2019,10/10/2019,"Fixed" +0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,2000,8/1/2019,10/10/2019,"Fixed" +0xac297053173b02b02a737d47f7b4a718e5b170ef,500,8/1/2019,10/10/2019,"Fixed" +0x49fc0b78238dab644698a90fa351b4c749e123d2,0.15,8/1/2019,20/10/2019,"Percentage" +0x10223927009b8add0960359dd90d1449415b7ca9,25,8/1/2019,10/10/2019,"Fixed" diff --git a/CLI/data/Transfer/VRTM/remove_custom_restriction_data.csv b/CLI/data/Transfer/VRTM/remove_custom_restriction_data.csv new file mode 100644 index 000000000..d927ee57b --- /dev/null +++ b/CLI/data/Transfer/VRTM/remove_custom_restriction_data.csv @@ -0,0 +1,2 @@ +0xabf60de3265b3017db7a1be66fc8b364ec1dbb98 +0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3 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/capped_sto_data.yml b/CLI/data/capped_sto_data.yml deleted file mode 100644 index 6be0c0f66..000000000 --- a/CLI/data/capped_sto_data.yml +++ /dev/null @@ -1,35 +0,0 @@ -securityToken: - # symbol -> security token symbol - symbol: CAP - # name -> security token name - name: CAP Token - # divisible -> true for divisible token, false for non-divisible - divisible: true - -initialMint: - # multimint -> whitelist and mint tokens to a list of affiliates (true) or only to a single wallet (false) - multimint: true - # singleMint -> Only valid if multimint = false - singleMint: - # wallet -> address that will be whitelisted - wallet: '' - # allowedToBuy -> true to allow wallet to buy tokens from the STO - allowedToBuy: true - # tokenAmount -> tokens minted to wallet - tokenAmount: 500000 - -sto: - # type -> 0 for cappedSTO, 1 for USDTieredSTO, 2 for select STO later - type: 0 - # cap -> - cap: 500000 - # startTime -> start time for the STO (Unix Epoch time) - startTime: '' - # endTime -> end time for the STO (Unix Epoch time) - endTime: '' - # wallet -> address that will receive the funds from the STO - wallet: '' - # raiseType -> 0 for ETH, 1 for POLY - raiseType: 0 - # rate -> rate for the STO - rate: 1000 \ No newline at end of file diff --git a/CLI/data/usd_tiered_sto_data.yml b/CLI/data/usd_tiered_sto_data.yml deleted file mode 100644 index 305167bd7..000000000 --- a/CLI/data/usd_tiered_sto_data.yml +++ /dev/null @@ -1,57 +0,0 @@ -securityToken: - # symbol -> security token symbol - symbol: TIE - # name -> security token name - name: TIEred Token - # divisible -> true for divisible token, false for non-divisible - divisible: true - -initialMint: - # multimint -> whitelist and mint tokens to a list of affiliates (true) or only to a single wallet (false) - multimint: true - # singleMint -> Only valid if multimint = false - singleMint: - # wallet -> address that will be whitelisted - wallet: '' - # allowedToBuy -> true to allow wallet to buy tokens from the STO - allowedToBuy: true - # tokenAmount -> tokens minted to wallet - tokenAmount: 500000 - -sto: - # type -> 0 for cappedSTO, 1 for USDTieredSTO, 2 for select STO later - type: 1 - - # fundings - # fundigType -> P for POLY, E for ETH, B for both - fundingType: B - - # addresses - # wallet -> address that will receive the funds from the STO - wallet: '' - # reserveWallet -> address that will receive remaining tokens in the case the cap is not met - reserveWallet: '' - - # tiers - # numberOfTiers -> total of tiers for the STO - numberOfTiers: 3 - # tokensPerTiers -> total tokens for each tier - tokensPerTiers: !!seq [ 190000000, 100000000, 200000000 ] - # ratePerTiers -> rate for each tier (in USD) - ratePerTiers: !!seq [ 0.05, 0.10, 0.15 ] - # discountedTokensPerTiers -> tokens for discounted rate for POLY investments (relative to tokensPerTier). 0 for no discounted tokens - discountedTokensPerTiers: !!seq [ 0, 0, 100000000 ] - # discountedRatePerTiers -> discounted rate for POLY investments for each tier. 0 for no discounted rate - discountedRatePerTiers: !!seq [ 0, 0, 0.075 ] - - # limits - # minimumInvestmentUSD -> minimum investment (in USD) - minimumInvestmentUSD: 5 - # nonAccreditedLimitUSD -> limit for non accredited insvestors (in USD) - nonAccreditedLimitUSD: 10000 - - #times - # startTime -> start time for the STO (Unix Epoch time) - startTime: '' - # endTime -> end time for the STO (Unix Epoch time) - endTime: '' \ No newline at end of file diff --git a/CLI/package.json b/CLI/package.json index 9eee76624..354ecc878 100644 --- a/CLI/package.json +++ b/CLI/package.json @@ -4,17 +4,21 @@ "description": "CLI for Polymath-core", "main": "polymath-cli.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "stable_coin": "scripts/stable_coin.sh" }, "author": "Polymath Inc", "license": "MIT", "dependencies": { + "bignumber.js": "^8.1.1", "chalk": "^2.4.1", "commander": "^2.16.0", + "csv-parse": "^4.0.1", + "ethers": "^4.0.7", "moment": "^2.22.2", "readline-sync": "^1.4.9", "request": "^2.88.0", "request-promise": "^4.2.2", - "web3": "1.0.0-beta.34" + "table": "^5.1.1", + "web3": "1.0.0-beta.35" } } diff --git a/CLI/polymath-cli.js b/CLI/polymath-cli.js index a4d912c82..66b8badf8 100644 --- a/CLI/polymath-cli.js +++ b/CLI/polymath-cli.js @@ -1,138 +1,145 @@ #!/usr/bin/env node -const shell = require('shelljs'); -var faucet = require('./commands/faucet'); -var investor_portal = require('./commands/investor_portal'); -var module_manager = require('./commands/module_manager'); -var st20generator = require('./commands/ST20Generator'); -var transfer = require('./commands/transfer'); -var transfer_ownership = require('./commands/transfer_ownership'); -var dividends_manager = require('./commands/dividends_manager'); -var transfer_manager = require('./commands/transfer_manager'); -var contract_manager = require('./commands/contract_manager'); -var strMigrator = require('./commands/strMigrator'); -var permission_manager = require('./commands/permission_manager'); -var program = require('commander'); +const faucet = require('./commands/faucet'); +const investor_portal = require('./commands/investor_portal'); +const token_manager = require('./commands/token_manager'); +const st20generator = require('./commands/ST20Generator'); +const sto_manager = require('./commands/sto_manager'); +const transfer = require('./commands/transfer'); +const transfer_ownership = require('./commands/transfer_ownership'); +const dividends_manager = require('./commands/dividends_manager'); +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 gbl = require('./commands/common/global'); +const program = require('commander'); +const moment = require('moment'); const yaml = require('js-yaml'); const fs = require('fs'); program - .version('0.0.1') + .version('1.0.1') .description('CLI for Polymath-core') - .option('-r, --remote-node ', 'Use Infura to connect to a remote node on selected network'); + .option('-r, --remote-node ', 'Connect to a remote node'); program .command('st20generator') .alias('st') - .option('-c, --config ', "Uses configuration file to configure ST and STO") + .option('-t, --ticker ', 'Unique token ticker') + .option('-o, --transferOwnership ', `Transfers the ticker's ownership to newOwner account. If newOwner is 'false', this step is skipped`) + .option('-n, --tokenName ', 'Token name') + .option('-d, --details
', 'Off-chain details of the token') + .option('-D, --divisible
', 'If token is divisible or not [true]', /^(true|false)/) .description('Wizard-like script that will guide technical users in the creation and deployment of an ST-20 token') - .action(async function(cmd) { - let tokenConfig; - let mintingConfig; - let stoCofig; - if (cmd.config) { - let config = yaml.safeLoad(fs.readFileSync(`${__dirname}/data/${cmd.config}`, 'utf8')); - tokenConfig = config.securityToken; - mintingConfig = config.initialMint; - stoCofig = config.sto; + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + await st20generator.executeApp(cmd.ticker, cmd.transferOwnership, cmd.tokenName, cmd.details, cmd.divisible); + }); + +program + .command('sto_manager') + .alias('sto') + .option('-t, --securityToken ', 'Selects a ST to manage modules') + .option('-l, --launch ', 'Uses configuration file to configure and launch a STO') + .description('Wizard-like script that will guide technical users in the creation of an STO') + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + if (cmd.launch) { + let config = yaml.safeLoad(fs.readFileSync(`${__dirname}/${cmd.launch}`, 'utf8')); + await sto_manager.addSTOModule(cmd.securityToken, config) + } else { + await sto_manager.executeApp(cmd.securityToken); } - await st20generator.executeApp(tokenConfig, mintingConfig, stoCofig, program.remoteNode); }); program .command('faucet [beneficiary] [amount]') .alias('f') .description('Poly faucet for local private netwtorks') - .action(async function(beneficiary, amount) { - await faucet.executeApp(beneficiary, amount, program.remoteNode); + .action(async function (beneficiary, amount) { + await gbl.initialize(program.remoteNode); + await faucet.executeApp(beneficiary, amount); }); program .command('investor_portal [investor] [privateKey] [symbol] [currency] [amount]') .alias('i') .description('Participate in any STO you have been whitelisted for') - .action(async function(investor, privateKey, symbol, currency, amount) { - await investor_portal.executeApp(investor, privateKey, symbol, currency, amount, program.remoteNode); + .action(async function (investor, privateKey, symbol, currency, amount) { + await gbl.initialize(program.remoteNode); + await investor_portal.executeApp(investor, privateKey, symbol, currency, amount); }); program - .command('module_manager') - .alias('mm') - .description('View modules attached to a token and their status') - .action(async function() { - await module_manager.executeApp(program.remoteNode); - }); - -program - .command('multi_mint [batchSize]') - .alias('mi') - .description('Distribute tokens to previously whitelisted investors') - .action(async function(tokenSymbol, batchSize) { - shell.exec(`${__dirname}/commands/scripts/script.sh Multimint ${tokenSymbol} ${batchSize} ${program.remoteNode}`);; + .command('token_manager') + .alias('stm') + .option('-t, --securityToken ', 'Selects a ST to manage') + .option('-m, --multiMint ', 'Distribute tokens to previously whitelisted investors') + .option('-b, --batchSize ', 'Max number of records per transaction') + .description('Manage your Security Tokens, mint tokens, add modules and change config') + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + if (cmd.multiMint) { + let batchSize = cmd.batchSize ? cmd.batchSize : gbl.constants.DEFAULT_BATCH_SIZE; + await token_manager.multiMint(cmd.securityToken, cmd.multiMint, batchSize); + } else { + await token_manager.executeApp(cmd.securityToken); + } }); program .command('transfer ') .alias('t') .description('Transfer ST tokens to another account') - .action(async function(tokenSymbol, transferTo, transferAmount) { - await transfer.executeApp(tokenSymbol, transferTo, transferAmount, program.remoteNode); + .action(async function (tokenSymbol, transferTo, transferAmount) { + await gbl.initialize(program.remoteNode); + await transfer.executeApp(tokenSymbol, transferTo, transferAmount); }); program .command('transfer_ownership ') .alias('to') .description('Transfer Ownership of an own contract to another account') - .action(async function(contractAddress, transferTo) { - await transfer_ownership.executeApp(contractAddress, transferTo, program.remoteNode); - }); - -program - .command('whitelist [batchSize]') - .alias('w') - .description('Mass-update a whitelist of allowed/known investors') - .action(async function(tokenSymbol, batchSize) { - shell.exec(`${__dirname}/commands/scripts/script.sh Whitelist ${tokenSymbol} ${batchSize} ${program.remoteNode}`); + .action(async function (contractAddress, transferTo) { + await gbl.initialize(program.remoteNode); + await transfer_ownership.executeApp(contractAddress, transferTo); }); program .command('dividends_manager [dividendsType]') .alias('dm') .description('Runs dividends_manager') - .action(async function(dividendsType) { - await dividends_manager.executeApp(dividendsType, program.remoteNode); + .action(async function (dividendsType) { + await gbl.initialize(program.remoteNode); + await dividends_manager.executeApp(dividendsType); }); program .command('transfer_manager') .alias('tm') + .option('-t, --securityToken ', 'Selects a ST to manage transfer modules') + .option('-w, --whitelist ', 'Whitelists addresses according to a csv file') + .option('-b, --batchSize ', 'Max number of records per transaction') .description('Runs transfer_manager') - .action(async function() { - await transfer_manager.executeApp(program.remoteNode); + .action(async function (cmd) { + await gbl.initialize(program.remoteNode); + if (cmd.whitelist) { + let batchSize = cmd.batchSize ? cmd.batchSize : gbl.constants.DEFAULT_BATCH_SIZE; + await transfer_manager.modifyWhitelistInBatch(cmd.securityToken, cmd.whitelist, batchSize); + } else { + await transfer_manager.executeApp(cmd.securityToken); + } }); program .command('contract_manager') .alias('cm') .description('Runs contract_manager') - .action(async function() { - await contract_manager.executeApp(program.remoteNode); - }); - -program - .command('accredit [batchSize]') - .alias('a') - .description('Runs accredit') - .action(async function(tokenSymbol, batchSize) { - shell.exec(`${__dirname}/commands/scripts/script.sh Accredit ${tokenSymbol} ${batchSize} ${program.remoteNode}`);; - }); - -program - .command('nonAccreditedLimit [batchSize]') - .alias('nal') - .description('Runs changeNonAccreditedLimit') - .action(async function(tokenSymbol, batchSize) { - shell.exec(`${__dirname}/commands/scripts/script.sh NonAccreditedLimit ${tokenSymbol} ${batchSize} ${program.remoteNode}`);; + .action(async function () { + await gbl.initialize(program.remoteNode); + await contract_manager.executeApp(); }); program @@ -142,7 +149,7 @@ program .option('-ot, --onlyTickers', 'Only migrate tickers without a launched token') .alias('str') .description('Runs STR Migrator') - .action(async function(toStrAddress, fromTrAddress, fromStrAddress, cmd) { + .action(async function (toStrAddress, fromTrAddress, fromStrAddress, cmd) { await strMigrator.executeApp(toStrAddress, fromTrAddress, fromStrAddress, cmd.singleTicker, cmd.tokenAddress, cmd.onlyTickers, program.remoteNode); }); @@ -150,10 +157,28 @@ program .command('permission_manager') .alias('pm') .description('Runs permission_manager') - .action(async function() { - await permission_manager.executeApp(program.remoteNode); + .action(async function () { + await gbl.initialize(program.remoteNode); + 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.parse(process.argv); if (typeof program.commands.length == 0) { diff --git a/CLI/scripts/stable_coin.sh b/CLI/scripts/stable_coin.sh new file mode 100755 index 000000000..98415d7cc --- /dev/null +++ b/CLI/scripts/stable_coin.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +# Exit script as soon as a command fails. +set -o errexit + +# Token symbol read from args. +TOKEN_SYMBOL=$1 + +# Paths +CWD="$(pwd)" +WHITELIST='/data/Transfer/GTM/whitelist_data.csv' +MULTIMINT='/data/ST/multi_mint_data.csv' + +# Scripts + +node polymath-cli st -t $TOKEN_SYMBOL -o false -n $TOKEN_SYMBOL -d '' -D true +node polymath-cli tm -t $TOKEN_SYMBOL -w $CWD$WHITELIST +node polymath-cli stm -t $TOKEN_SYMBOL -m $CWD$MULTIMINT diff --git a/CLI/yarn.lock b/CLI/yarn.lock index 9a48fd1a5..2cb4b3482 100644 --- a/CLI/yarn.lock +++ b/CLI/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@types/node@^10.3.2": + version "10.12.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235" + integrity sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ== + accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" @@ -10,6 +15,11 @@ accepts@~1.3.5: mime-types "~2.1.18" negotiator "0.6.1" +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0= + ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" @@ -20,7 +30,22 @@ ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ansi-styles@^3.2.1: +ajv@^6.6.1: + version "6.6.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61" + integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -58,6 +83,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" @@ -100,6 +130,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bignumber.js@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.1.1.tgz#4b072ae5aea9c20f6730e4e5d529df1271c4d885" + integrity sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ== + bl@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -361,6 +396,11 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= +cookiejar@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" + integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -422,6 +462,11 @@ crypto-browserify@3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +csv-parse@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.0.1.tgz#4ad438352cbf12d5317d0fb9d588e53473293851" + integrity sha512-ehkwejEj05wwO7Q9JD+YSI6dNMIauHIroNU1RALrmRrqPoZIwRnfBtgq5GkU6i2RxZOJqjo3dtI1NrVSXvaimA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -556,6 +601,16 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +elliptic@6.3.3: + version "6.3.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" + integrity sha1-VILZZG1UvLif19mU/J4ulWiHbj8= + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + inherits "^2.0.1" + elliptic@^6.0.0, elliptic@^6.4.0: version "6.4.1" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" @@ -618,6 +673,22 @@ eth-lib@0.2.7: elliptic "^6.4.0" xhr-request-promise "^0.1.2" +ethers@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.7.tgz#8c653618077a1fc60c5c1b2575da03561f0039f4" + integrity sha512-HGR2FcyeiRjg6qZUjtK5gkTp7puB+KPhBDa0s2zG4IMVcfU33rkhl9yBIqZBzpD5J0fE33gQ57+epZh90nBaeA== + dependencies: + "@types/node" "^10.3.2" + aes-js "3.0.0" + bn.js "^4.4.0" + elliptic "6.3.3" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -695,6 +766,11 @@ fast-deep-equal@^1.0.0: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -912,6 +988,14 @@ hash-base@^3.0.0: inherits "^2.0.1" safe-buffer "^5.0.1" +hash.js@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.5" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" @@ -988,6 +1072,11 @@ is-callable@^1.1.3: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + is-function@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" @@ -1046,6 +1135,11 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +js-sha3@0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" + integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc= + js-sha3@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" @@ -1061,6 +1155,11 @@ json-schema-traverse@^0.3.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -1096,7 +1195,7 @@ keccakjs@^0.2.1: browserify-sha3 "^0.0.1" sha3 "^1.1.0" -lodash@^4.13.1: +lodash@^4.13.1, lodash@^4.17.11: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -1437,6 +1536,11 @@ punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + qs@6.5.2, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -1572,6 +1676,11 @@ safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +scrypt-js@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" + integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== + scrypt.js@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada" @@ -1641,6 +1750,11 @@ servify@^0.1.12: request "^2.79.0" xhr "^2.3.3" +setimmediate@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" + integrity sha1-IOgd5iLUoCWIzgyNqJc8vPHTE48= + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -1680,6 +1794,15 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +slice-ansi@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" + integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + sshpk@^1.7.0: version "1.15.2" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" @@ -1715,6 +1838,14 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -1722,6 +1853,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + strip-dirs@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" @@ -1762,6 +1900,16 @@ swarm-js@0.1.37: tar.gz "^1.0.5" xhr-request-promise "^0.1.2" +table@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" + integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw== + dependencies: + ajv "^6.6.1" + lodash "^4.17.11" + slice-ansi "2.0.0" + string-width "^2.1.1" + tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" @@ -1887,6 +2035,13 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" @@ -1943,87 +2098,87 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -web3-bzz@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.0.0-beta.34.tgz#068d37777ab65e5c60f8ec8b9a50cfe45277929c" - integrity sha1-Bo03d3q2Xlxg+OyLmlDP5FJ3kpw= +web3-bzz@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.0.0-beta.35.tgz#9d5e1362b3db2afd77d65619b7cd46dd5845c192" + integrity sha512-BhAU0qhlr8zltm4gs/+P1gki2VkxHJaM2Rrh4DGesDW0lzwufRoNvWFlwx1bKHoFPWNbSmm9PRkHOYOINL/Tgw== dependencies: got "7.1.0" swarm-js "0.1.37" underscore "1.8.3" -web3-core-helpers@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.34.tgz#b168da00d3e19e156bc15ae203203dd4dfee2d03" - integrity sha1-sWjaANPhnhVrwVriAyA91N/uLQM= +web3-core-helpers@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.35.tgz#d681d218a0c6e3283ee1f99a078ab9d3eef037f1" + integrity sha512-APOu3sEsamyqWt//8o4yq9KF25/uqGm+pQShson/sC4gKzmfJB07fLo2ond0X30E8fIqAPeVCotPXQxGciGUmA== dependencies: underscore "1.8.3" - web3-eth-iban "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-eth-iban "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-core-method@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.0.0-beta.34.tgz#ec163c8a2c490fa02a7ec15559fa7307fc7cc6dd" - integrity sha1-7BY8iixJD6AqfsFVWfpzB/x8xt0= +web3-core-method@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.0.0-beta.35.tgz#fc10e2d546cf4886038e6130bd5726b0952a4e5f" + integrity sha512-jidImCide8q0GpfsO4L73qoHrbkeWgwU3uOH5DKtJtv0ccmG086knNMRgryb/o9ZgetDWLmDEsJnHjBSoIwcbA== dependencies: underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" - web3-core-promievent "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-core-promievent "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-core-promievent@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.0.0-beta.34.tgz#a4f4fa6784bb293e82c60960ae5b56a94cd03edc" - integrity sha1-pPT6Z4S7KT6CxglgrltWqUzQPtw= +web3-core-promievent@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.0.0-beta.35.tgz#4f1b24737520fa423fee3afee110fbe82bcb8691" + integrity sha512-GvqXqKq07OmHuVi5uNRg6k79a1/CI0ViCC+EtNv4CORHtDRmYEt5Bvdv6z6FJEiaaQkD0lKbFwNhLxutx7HItw== dependencies: any-promise "1.3.0" eventemitter3 "1.1.1" -web3-core-requestmanager@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.0.0-beta.34.tgz#01f8f6cf2ae6b6f0b70c38bae1ef741b5bab215c" - integrity sha1-Afj2zyrmtvC3DDi64e90G1urIVw= +web3-core-requestmanager@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.0.0-beta.35.tgz#2b77cbf6303720ad68899b39fa7f584dc03dbc8f" + integrity sha512-S+zW2h17ZZQU9oe3yaCJE0E7aJS4C3Kf4kGPDv+nXjW0gKhQQhgVhw1Doq/aYQGqNSWJp7f1VHkz5gQWwg6RRg== dependencies: underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" - web3-providers-http "1.0.0-beta.34" - web3-providers-ipc "1.0.0-beta.34" - web3-providers-ws "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-providers-http "1.0.0-beta.35" + web3-providers-ipc "1.0.0-beta.35" + web3-providers-ws "1.0.0-beta.35" -web3-core-subscriptions@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.0.0-beta.34.tgz#9fed144033f221c3cf21060302ffdaf5ef2de2de" - integrity sha1-n+0UQDPyIcPPIQYDAv/a9e8t4t4= +web3-core-subscriptions@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.0.0-beta.35.tgz#c1b76a2ad3c6e80f5d40b8ba560f01e0f4628758" + integrity sha512-gXzLrWvcGkGiWq1y33Z4Y80XI8XMrwowiQJkrPSjQ81K5PBKquOGwcMffLaKcwdmEy/NpsOXDeFo3eLE1Ghvvw== dependencies: eventemitter3 "1.1.1" underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" -web3-core@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.0.0-beta.34.tgz#121be8555e9fb00d2c5d05ddd3381d0c9e46987e" - integrity sha1-EhvoVV6fsA0sXQXd0zgdDJ5GmH4= +web3-core@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.0.0-beta.35.tgz#0c44d3c50d23219b0b1531d145607a9bc7cd4b4f" + integrity sha512-ayGavbgVk4KL9Y88Uv411fBJ0SVgVfKhKEBweKYzmP0zOqneMzWt6YsyD1n6kRvjAbqA0AfUPEOKyMNjcx2tjw== dependencies: - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-requestmanager "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-requestmanager "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth-abi@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.34.tgz#034533e3aa2f7e59ff31793eaea685c0ed5af67a" - integrity sha1-A0Uz46ovfln/MXk+rqaFwO1a9no= +web3-eth-abi@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.35.tgz#2eb9c1c7c7233db04010defcb192293e0db250e6" + integrity sha512-KUDC+EtFFYG8z01ZleKrASdjj327/rtWHzEt6RWsEj7bBa0bGp9nEh+nqdZx/Sdgz1O8tnfFzJlrRcXpfr1vGg== dependencies: bn.js "4.11.6" underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth-accounts@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.34.tgz#e09142eeecc797ac3459b75e9b23946d3695f333" - integrity sha1-4JFC7uzHl6w0WbdemyOUbTaV8zM= +web3-eth-accounts@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.35.tgz#7d0e5a69f510dc93874471599eb7abfa9ddf3e63" + integrity sha512-duIgRsfht/0kAW/eQ0X9lKtVIykbETrnM2H7EnvplCzPHtQLodpib4o9JXfh9n6ZDgdDC7cuJoiVB9QJg089ew== dependencies: any-promise "1.3.0" crypto-browserify "3.12.0" @@ -2031,111 +2186,111 @@ web3-eth-accounts@1.0.0-beta.34: scrypt.js "0.2.0" underscore "1.8.3" uuid "2.0.1" - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth-contract@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.0.0-beta.34.tgz#9dbb38fae7643a808427a20180470ec7415c91e6" - integrity sha1-nbs4+udkOoCEJ6IBgEcOx0FckeY= +web3-eth-contract@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.0.0-beta.35.tgz#5276242d8a3358d9f1ce92b71575c74f9015935c" + integrity sha512-foPohOg5O1UCGKGZOIs+kQK5IZdV2QQ7pAWwNxH8WHplUA+fre1MurXNpoxknUmH6mYplFhXjqgYq2MsrBpHrA== dependencies: underscore "1.8.3" - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-promievent "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-eth-abi "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" - -web3-eth-iban@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.34.tgz#9af458605867ccf74ea979aaf326b38ba6a5ba0c" - integrity sha1-mvRYYFhnzPdOqXmq8yazi6alugw= + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-promievent "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-eth-abi "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" + +web3-eth-iban@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.35.tgz#5aa10327a9abb26bcfc4ba79d7bad18a002b332c" + integrity sha512-H5wkcNcAIc+h/WoDIKv7ZYmrM2Xqu3O7jBQl1IWo73EDVQji+AoB2i3J8tuwI1yZRInRwrfpI3Zuwuf54hXHmQ== dependencies: bn.js "4.11.6" - web3-utils "1.0.0-beta.34" + web3-utils "1.0.0-beta.35" -web3-eth-personal@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.0.0-beta.34.tgz#9afba167342ebde5420bcd5895c3f6c34388f205" - integrity sha1-mvuhZzQuveVCC81YlcP2w0OI8gU= +web3-eth-personal@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.0.0-beta.35.tgz#ecac95b7a53d04a567447062d5cae5f49879e89f" + integrity sha512-AcM9nnlxu7ZRRxPvkrFB9eLxMM4A2cPfj2aCg21Wb2EpMnhR+b/O1cT33k7ApRowoMpM+T9M8vx2oPNwXfaCOQ== dependencies: - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-net "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-net "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" -web3-eth@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.0.0-beta.34.tgz#74086000850c6fe6f535ef49837d6d4bb6113268" - integrity sha1-dAhgAIUMb+b1Ne9Jg31tS7YRMmg= +web3-eth@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.0.0-beta.35.tgz#c52c804afb95e6624b6f5e72a9af90fbf5005b68" + integrity sha512-04mcb2nGPXThawuuYICPOxv0xOHofvQKsjZeIq+89nyOC8DQMGTAErDkGyMHQYtjpth5XDhic0wuEsA80AmFZA== dependencies: underscore "1.8.3" - web3-core "1.0.0-beta.34" - web3-core-helpers "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-eth-abi "1.0.0-beta.34" - web3-eth-accounts "1.0.0-beta.34" - web3-eth-contract "1.0.0-beta.34" - web3-eth-iban "1.0.0-beta.34" - web3-eth-personal "1.0.0-beta.34" - web3-net "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" - -web3-net@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.0.0-beta.34.tgz#427cea2f431881449c8e38d523290f173f9ff63d" - integrity sha1-QnzqL0MYgUScjjjVIykPFz+f9j0= - dependencies: - web3-core "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" - -web3-providers-http@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.34.tgz#e561b52bbb43766282007d40285bfe3550c27e7a" - integrity sha1-5WG1K7tDdmKCAH1AKFv+NVDCfno= - dependencies: - web3-core-helpers "1.0.0-beta.34" - xhr2 "0.1.4" - -web3-providers-ipc@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.0.0-beta.34.tgz#a1b77f1a306d73649a9c039052e40cb71328d00a" - integrity sha1-obd/GjBtc2SanAOQUuQMtxMo0Ao= + web3-core "1.0.0-beta.35" + web3-core-helpers "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-eth-abi "1.0.0-beta.35" + web3-eth-accounts "1.0.0-beta.35" + web3-eth-contract "1.0.0-beta.35" + web3-eth-iban "1.0.0-beta.35" + web3-eth-personal "1.0.0-beta.35" + web3-net "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" + +web3-net@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.0.0-beta.35.tgz#5c6688e0dea71fcd910ee9dc5437b94b7f6b3354" + integrity sha512-bbwaQ/KohGjIJ6HAKbZ6KrklCAaG6/B7hIbAbVLSFLxF+Yz9lmAgQYaDInpidpC/NLb3WOmcbRF+P77J4qMVIA== + dependencies: + web3-core "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" + +web3-providers-http@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.35.tgz#92059d9d6de6e9f82f4fae30b743efd841afc1e1" + integrity sha512-DcIMFq52Fb08UpWyZ3ZlES6NsNqJnco4hBS/Ej6eOcASfuUayPI+GLkYVZsnF3cBYqlH+DOKuArcKSuIxK7jIA== + dependencies: + web3-core-helpers "1.0.0-beta.35" + xhr2-cookies "1.1.0" + +web3-providers-ipc@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.0.0-beta.35.tgz#031afeb10fade2ebb0ef2fb82f5e58c04be842d9" + integrity sha512-iB0FG0HcpUnayfa8pn4guqEQ4Y1nrroi/jffdtQgFkrNt0sD3fMSwwC0AbmECqj3tDLl0e1slBR0RENll+ZF0g== dependencies: oboe "2.1.3" underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" -web3-providers-ws@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.0.0-beta.34.tgz#7de70f1b83f2de36476772156becfef6e3516eb3" - integrity sha1-fecPG4Py3jZHZ3IVa+z+9uNRbrM= +web3-providers-ws@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.0.0-beta.35.tgz#5d38603fd450243a26aae0ff7f680644e77fa240" + integrity sha512-Cx64NgDStynKaUGDIIOfaCd0fZusL8h5avKTkdTjUu2aHhFJhZoVBGVLhoDtUaqZGWIZGcBJOoVf2JkGUOjDRQ== dependencies: underscore "1.8.3" - web3-core-helpers "1.0.0-beta.34" + web3-core-helpers "1.0.0-beta.35" websocket "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" -web3-shh@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.0.0-beta.34.tgz#975061d71eaec42ccee576f7bd8f70f03844afe0" - integrity sha1-l1Bh1x6uxCzO5Xb3vY9w8DhEr+A= +web3-shh@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.0.0-beta.35.tgz#7e4a585f8beee0c1927390937c6537748a5d1a58" + integrity sha512-8qSonk/x0xabERS9Sr6AIADN/Ty+5KwARkkGIfSYHKqFpdMDz+76F7cUCxtoCZoS8K04xgZlDKYe0TJXLYA0Fw== dependencies: - web3-core "1.0.0-beta.34" - web3-core-method "1.0.0-beta.34" - web3-core-subscriptions "1.0.0-beta.34" - web3-net "1.0.0-beta.34" + web3-core "1.0.0-beta.35" + web3-core-method "1.0.0-beta.35" + web3-core-subscriptions "1.0.0-beta.35" + web3-net "1.0.0-beta.35" -web3-utils@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.34.tgz#9411fc39aaef39ca4e06169f762297d9ff020970" - integrity sha1-lBH8OarvOcpOBhafdiKX2f8CCXA= +web3-utils@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.35.tgz#ced9e1df47c65581c441c5f2af76b05a37a273d7" + integrity sha512-Dq6f0SOKj3BDFRgOPnE6ALbzBDCKVIW8mKWVf7tGVhTDHf+wQaWwQSC3aArFSqdExB75BPBPyDpuMTNszhljpA== dependencies: bn.js "4.11.6" eth-lib "0.1.27" @@ -2145,18 +2300,18 @@ web3-utils@1.0.0-beta.34: underscore "1.8.3" utf8 "2.1.1" -web3@1.0.0-beta.34: - version "1.0.0-beta.34" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.34.tgz#347e561b784098cb5563315f490479a1d91f2ab1" - integrity sha1-NH5WG3hAmMtVYzFfSQR5odkfKrE= +web3@1.0.0-beta.35: + version "1.0.0-beta.35" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.35.tgz#6475095bd451a96e50a32b997ddee82279292f11" + integrity sha512-xwDmUhvTcHQvvNnOPcPZZgCxKUsI2e+GbHy7JkTK3/Rmnutazy8x7fsAXT9myw7V1qpi3GgLoZ3fkglSUbg1Mg== dependencies: - web3-bzz "1.0.0-beta.34" - web3-core "1.0.0-beta.34" - web3-eth "1.0.0-beta.34" - web3-eth-personal "1.0.0-beta.34" - web3-net "1.0.0-beta.34" - web3-shh "1.0.0-beta.34" - web3-utils "1.0.0-beta.34" + web3-bzz "1.0.0-beta.35" + web3-core "1.0.0-beta.35" + web3-eth "1.0.0-beta.35" + web3-eth-personal "1.0.0-beta.35" + web3-net "1.0.0-beta.35" + web3-shh "1.0.0-beta.35" + web3-utils "1.0.0-beta.35" "websocket@git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible": version "1.0.26" @@ -2201,10 +2356,12 @@ xhr-request@^1.0.1: url-set-query "^1.0.0" xhr "^2.0.4" -xhr2@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" - integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8= +xhr2-cookies@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" + integrity sha1-fXdEnQmZGX8VXLc7I99yUF7YnUg= + dependencies: + cookiejar "^2.1.1" xhr@^2.0.4, xhr@^2.3.3: version "2.5.0" @@ -2216,6 +2373,11 @@ xhr@^2.0.4, xhr@^2.3.3: parse-headers "^2.0.0" xtend "^4.0.0" +xmlhttprequest@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw= + xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" diff --git a/README.md b/README.md index 51610e9d4..5e7d97328 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ You can easily navigate through it with the sidebar directory in order to run th ## Mainnet -### v2.0.0 +### v2.1.0 | Contract | Address | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | @@ -127,15 +127,15 @@ You can easily navigate through it with the sidebar directory in order to run th | Feature Registry: | [0xa3eacb03622bf1513880892b7270d965f693ffb5](https://etherscan.io/address/0xa3eacb03622bf1513880892b7270d965f693ffb5) | | ETHOracle: | [0x60055e9a93aae267da5a052e95846fa9469c0e7a](https://etherscan.io/address/0x60055e9a93aae267da5a052e95846fa9469c0e7a) | | POLYOracle: | [0x52cb4616E191Ff664B0bff247469ce7b74579D1B](https://etherscan.io/address/0x52cb4616E191Ff664B0bff247469ce7b74579D1B) | -| General Transfer Manager Factory: | [0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26](https://etherscan.io/address/0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26) | +| General Transfer Manager Factory (2.0.0): | [0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26](https://etherscan.io/address/0xdc95598ef2bbfdb66d02d5f3eea98ea39fbc8b26) | +| General Transfer Manager Factory (2.1.0): | [0xa8b60c9b7054782f46931e35e7012037a574ecee](https://etherscan.io/address/0xa8b60c9b7054782f46931e35e7012037a574ecee) | | General Permission Manager Factory: | [0xf0aa1856360277c60052d6095c5b787b01388cdd](https://etherscan.io/address/0xf0aa1856360277c60052d6095c5b787b01388cdd) | -| CappedSTOFactory: | [0x77d89663e8819023a87bfe2bc9baaa6922c0e57c](https://etherscan.io/address/0x77d89663e8819023a87bfe2bc9baaa6922c0e57c) | -| USDTieredSTO Factory: | [0x5a3a30bddae1f857a19b1aed93b5cdb3c3da809a](https://etherscan.io/address/0x5a3a30bddae1f857a19b1aed93b5cdb3c3da809a) | -| EthDividendsCheckpointFactory: | [0x968c74c52f15b2de323eca8c677f6c9266bfefd6](https://etherscan.io/address/0x968c74c52f15b2de323eca8c677f6c9266bfefd6) | -| ERC20 Dividends Checkpoint Factory: | [0x82f9f1ab41bacb1433c79492e54bf13bccd7f9ae](https://etherscan.io/address/0x82f9f1ab41bacb1433c79492e54bf13bccd7f9ae) | -| Count Transfer Manager Factory: | [0xd9fd7e34d6e2c47a69e02131cf8554d52c3445d5](https://etherscan.io/address/0xd9fd7e34d6e2c47a69e02131cf8554d52c3445d5) | +| CappedSTOFactory (2.1.0): | [0x26bcd18a748ade7fafb250bb014c7121a412870c](https://etherscan.io/address/0x26bcd18a748ade7fafb250bb014c7121a412870c) | +| USDTieredSTO Factory (2.1.0): | [0x0148ed61ffa8e0da7908a62aa2d1f266688656c4](https://etherscan.io/address/0x0148ed61ffa8e0da7908a62aa2d1f266688656c4) | +| ERC20 Dividends Checkpoint Factory (2.1.0): | [0x855152c9b98babadc996a0dd71c8b3682b8f4786](https://etherscan.io/address/0x855152c9b98babadc996a0dd71c8b3682b8f4786) | +| Count Transfer Manager Factory (2.1.0): | [0x575ed30ec5700f369115b079be8059001e79eb7b](https://etherscan.io/address/0x575ed30ec5700f369115b079be8059001e79eb7b) | | Percentage Transfer Manager Factory: | [0xe6267a9c0a227d21c95b782b1bd32bb41fc3b43b](https://etherscan.io/address/0xe6267a9c0a227d21c95b782b1bd32bb41fc3b43b) | -| Manual Approval Transfer Manager Factory (2.0.1): | [0x6af2afad53cb334e62b90ddbdcf3a086f654c298](https://etherscan.io/address/0x6af2afad53cb334e62b90ddbdcf3a086f654c298) | +| Manual Approval Transfer Manager Factory (2.1.0): | [0xe5b21cf83f5e49aba4601e8d8cf182f889208cfd](https://etherscan.io/address/0xe5b21cf83f5e49aba4601e8d8cf182f889208cfd) | New SecurityTokenRegistry (2.0.1): 0x538136ed73011a766bf0a126a27300c3a7a2e6a6 @@ -144,37 +144,28 @@ New SecurityTokenRegistry (2.0.1): 0x538136ed73011a766bf0a126a27300c3a7a2e6a6 New ModuleRegistry (2.0.1): 0xbc18f144ccf87f2d98e6fa0661799fcdc3170119 (fixed bug with missing transferOwnership function) -New ManualApprovalTransferManager 0x6af2afad53cb334e62b90ddbdcf3a086f654c298 -(Fixed 0x0 from bug) - ## KOVAN -### v2.0.0 +### v2.1.0 New Kovan PolyTokenFaucet: 0xb347b9f5b56b431b2cf4e1d90a5995f7519ca792 | Contract | Address | | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| SecurityTokenRegistry (Proxy): | [0xbefb81114d532bddddc724af20c3516fa75f0afb](https://kovan.etherscan.io/address/0xbefb81114d532bddddc724af20c3516fa75f0afb) | -| ModuleRegistry (Proxy): | [0x0fac8d8cce224eead73c1187df96570aa80a568b](https://kovan.etherscan.io/address/0x0fac8d8cce224eead73c1187df96570aa80a568b) | -| Polymath Registry: | [0x9903e7b5acfe5fa9713771a8d861eb1df8cd7046](https://kovan.etherscan.io/address/0x9903e7b5acfe5fa9713771a8d861eb1df8cd7046) | -| Feature Registry: | [0xa8f85006fdacb3d59ffae564c05433f0c949e911](https://kovan.etherscan.io/address/0xa8f85006fdacb3d59ffae564c05433f0c949e911) | +| SecurityTokenRegistry (Proxy): | [0x91110c2f67e2881a8540417be9eadf5bc9f2f248](https://kovan.etherscan.io/address/0x91110c2f67e2881a8540417be9eadf5bc9f2f248) | +| ModuleRegistry (Proxy): | [0xde6d19d7a68d453244227b6ccc5d8e6c2314627a](https://kovan.etherscan.io/address/0xde6d19d7a68d453244227b6ccc5d8e6c2314627a) | +| Polymath Registry: | [0x5b215a7d39ee305ad28da29bf2f0425c6c2a00b3](https://kovan.etherscan.io/address/0x5b215a7d39ee305ad28da29bf2f0425c6c2a00b3) | +| Feature Registry: | [0x8967a7cfc4b455398be2356cd05cd43b7a39697e](https://kovan.etherscan.io/address/0x8967a7cfc4b455398be2356cd05cd43b7a39697e) | | ETHOracle: | [0xCE5551FC9d43E9D2CC255139169FC889352405C8](https://kovan.etherscan.io/address/0xCE5551FC9d43E9D2CC255139169FC889352405C8) | | POLYOracle: | [0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C](https://kovan.etherscan.io/address/0x461d98EF2A0c7Ac1416EF065840fF5d4C946206C) | -| General Transfer Manager Factory: | [0xfe7e2bb6c200d5222c82d0f8fecca5f8fe4ab8ce](https://kovan.etherscan.io/address/0xfe7e2bb6c200d5222c82d0f8fecca5f8fe4ab8ce) | -| General Permission Manager Factory: | [0xde5eaa8d73f43fc5e7badb203f03ecae2b29bd92](https://kovan.etherscan.io/address/0xde5eaa8d73f43fc5e7badb203f03ecae2b29bd92) | -| CappedSTOFactory: | [0xe14d7dd044cc6cfe37548b6791416c59f19bfc0d](https://kovan.etherscan.io/address/0xe14d7dd044cc6cfe37548b6791416c59f19bfc0d) | -| USDTieredSTO Factory: | [0xf9f0bb9f868d411dd9a9511a79d172449e3c15f5](https://kovan.etherscan.io/address/0xf9f0bb9f868d411dd9a9511a79d172449e3c15f5) | -| EthDividendsCheckpointFactory: | [0x2861425ba5abbf50089c473b28f6c40a8ea5262a](https://kovan.etherscan.io/address/0x2861425ba5abbf50089c473b28f6c40a8ea5262a) | -| ERC20 Dividends Checkpoint Factory: | [0xbf9495550417feaacc43f86d2244581b6d688431](https://kovan.etherscan.io/address/0xbf9495550417feaacc43f86d2244581b6d688431) | -| Count Transfer Manager Factory: | [0x3c3c1f40ae2bdca82b90541b2cfbd41caa941c0e](https://kovan.etherscan.io/address/0x3c3c1f40ae2bdca82b90541b2cfbd41caa941c0e) | -| Percentage Transfer Manager Factory: | [0x8cd00c3914b2967a8b79815037f51c76874236b8](https://kovan.etherscan.io/address/0x8cd00c3914b2967a8b79815037f51c76874236b8) | +| General Transfer Manager Factory: | [0x650e9507e983077d6f822472a7dcc37626d55c7f](https://kovan.etherscan.io/address/0x650e9507e983077d6f822472a7dcc37626d55c7f) | +| General Permission Manager Factory: | [0xbf0bd6305b523ce055baa6dfaa9676d6b9e6090b](https://kovan.etherscan.io/address/0xbf0bd6305b523ce055baa6dfaa9676d6b9e6090b) | +| CappedSTOFactory: | [0x01510b2c03296473f883c12d0723f0a46aa67f13](https://kovan.etherscan.io/address/0x01510b2c03296473f883c12d0723f0a46aa67f13) | +| USDTieredSTO Factory: | [0x8b9743e6129f7b7cca04e3611b5c8cd9b1d11e90](https://kovan.etherscan.io/address/0x8b9743e6129f7b7cca04e3611b5c8cd9b1d11e90) | +| ERC20 Dividends Checkpoint Factory: | [0x4369751df5bcb2f12f1790f525ef212a622b9c60](https://kovan.etherscan.io/address/0x4369751df5bcb2f12f1790f525ef212a622b9c60) | +| Count Transfer Manager Factory: | [0xc7cf0c1ddc85c18672951f9bfeb7163ecc8f0e2f](https://kovan.etherscan.io/address/0xc7cf0c1ddc85c18672951f9bfeb7163ecc8f0e2f) | +| Percentage Transfer Manager Factory: | [0xfea5fcb254bcb4ada0f86903ff822d6372325cb1](https://kovan.etherscan.io/address/0xfea5fcb254bcb4ada0f86903ff822d6372325cb1) | | Manual Approval Transfer Manager Factory: | [0x9faa79e2ccf0eb49aa6ebde1795ad2e951ce78f8](https://kovan.etherscan.io/address/0x9faa79e2ccf0eb49aa6ebde1795ad2e951ce78f8) | - -New ManualApprovalTransferManager 0x9faa79e2ccf0eb49aa6ebde1795ad2e951ce78f8 -(Fixed 0x0 from bug) - - ## Package version requirements for your machine: - node v8.x.x or v9.x.x diff --git a/contracts/ModuleRegistry.sol b/contracts/ModuleRegistry.sol index 65de42fa6..966937c52 100644 --- a/contracts/ModuleRegistry.sol +++ b/contracts/ModuleRegistry.sol @@ -105,7 +105,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { } function initialize(address _polymathRegistry, address _owner) external payable { - require(!getBool(Encoder.getKey("initialised")),"already initialized"); + require(!getBoolValue(Encoder.getKey("initialised")),"already initialized"); require(_owner != address(0) && _polymathRegistry != address(0), "0x address is invalid"); set(Encoder.getKey("polymathRegistry"), _polymathRegistry); set(Encoder.getKey("owner"), _owner); @@ -122,11 +122,11 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { */ function useModule(address _moduleFactory) external { // This if statement is required to be able to add modules from the token proxy contract during deployment - if (ISecurityTokenRegistry(getAddress(Encoder.getKey("securityTokenRegistry"))).isSecurityToken(msg.sender)) { - if (IFeatureRegistry(getAddress(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed")) { - require(getBool(Encoder.getKey("verified", _moduleFactory)) || IOwnable(_moduleFactory).owner() == IOwnable(msg.sender).owner(),"ModuleFactory must be verified or SecurityToken owner must be ModuleFactory owner"); + if (ISecurityTokenRegistry(getAddressValue(Encoder.getKey("securityTokenRegistry"))).isSecurityToken(msg.sender)) { + if (IFeatureRegistry(getAddressValue(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed")) { + require(getBoolValue(Encoder.getKey("verified", _moduleFactory)) || IOwnable(_moduleFactory).owner() == IOwnable(msg.sender).owner(),"ModuleFactory must be verified or SecurityToken owner must be ModuleFactory owner"); } else { - require(getBool(Encoder.getKey("verified", _moduleFactory)), "ModuleFactory must be verified"); + require(getBoolValue(Encoder.getKey("verified", _moduleFactory)), "ModuleFactory must be verified"); } require(_isCompatibleModule(_moduleFactory, msg.sender), "Version should within the compatible range of ST"); pushArray(Encoder.getKey("reputation", _moduleFactory), msg.sender); @@ -148,12 +148,12 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @param _moduleFactory is the address of the module factory to be registered */ function registerModule(address _moduleFactory) external whenNotPausedOrOwner { - if (IFeatureRegistry(getAddress(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed")) { + if (IFeatureRegistry(getAddressValue(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed")) { require(msg.sender == IOwnable(_moduleFactory).owner() || msg.sender == owner(),"msg.sender must be the Module Factory owner or registry curator"); } else { require(msg.sender == owner(), "Only owner allowed to register modules"); } - require(getUint(Encoder.getKey("registry", _moduleFactory)) == 0, "Module factory should not be pre-registered"); + require(getUintValue(Encoder.getKey("registry", _moduleFactory)) == 0, "Module factory should not be pre-registered"); IModuleFactory moduleFactory = IModuleFactory(_moduleFactory); //Enforce type uniqueness uint256 i; @@ -181,14 +181,14 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @param _moduleFactory is the address of the module factory to be deleted from the registry */ function removeModule(address _moduleFactory) external whenNotPausedOrOwner { - uint256 moduleType = getUint(Encoder.getKey("registry", _moduleFactory)); + uint256 moduleType = getUintValue(Encoder.getKey("registry", _moduleFactory)); require(moduleType != 0, "Module factory should be registered"); require( msg.sender == IOwnable(_moduleFactory).owner() || msg.sender == owner(), "msg.sender must be the Module Factory owner or registry curator" ); - uint256 index = getUint(Encoder.getKey("moduleListIndex", _moduleFactory)); + uint256 index = getUintValue(Encoder.getKey("moduleListIndex", _moduleFactory)); uint256 last = getArrayAddress(Encoder.getKey("moduleList", moduleType)).length - 1; address temp = getArrayAddress(Encoder.getKey("moduleList", moduleType))[last]; @@ -220,7 +220,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return bool */ function verifyModule(address _moduleFactory, bool _verified) external onlyOwner { - require(getUint(Encoder.getKey("registry", _moduleFactory)) != uint256(0), "Module factory must be registered"); + require(getUintValue(Encoder.getKey("registry", _moduleFactory)) != uint256(0), "Module factory must be registered"); set(Encoder.getKey("verified", _moduleFactory), _verified); emit ModuleVerified(_moduleFactory, _verified); } @@ -303,15 +303,15 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { function getModulesByTypeAndToken(uint8 _moduleType, address _securityToken) public view returns (address[]) { uint256 _len = getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType))).length; address[] memory _addressList = getArrayAddress(Encoder.getKey("moduleList", uint256(_moduleType))); - bool _isCustomModuleAllowed = IFeatureRegistry(getAddress(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed"); + bool _isCustomModuleAllowed = IFeatureRegistry(getAddressValue(Encoder.getKey("featureRegistry"))).getFeatureStatus("customModulesAllowed"); uint256 counter = 0; for (uint256 i = 0; i < _len; i++) { if (_isCustomModuleAllowed) { - if (IOwnable(_addressList[i]).owner() == IOwnable(_securityToken).owner() || getBool(Encoder.getKey("verified", _addressList[i]))) + if (IOwnable(_addressList[i]).owner() == IOwnable(_securityToken).owner() || getBoolValue(Encoder.getKey("verified", _addressList[i]))) if(_isCompatibleModule(_addressList[i], _securityToken)) counter++; } - else if (getBool(Encoder.getKey("verified", _addressList[i]))) { + else if (getBoolValue(Encoder.getKey("verified", _addressList[i]))) { if(_isCompatibleModule(_addressList[i], _securityToken)) counter++; } @@ -320,14 +320,14 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { counter = 0; for (uint256 j = 0; j < _len; j++) { if (_isCustomModuleAllowed) { - if (IOwnable(_addressList[j]).owner() == IOwnable(_securityToken).owner() || getBool(Encoder.getKey("verified", _addressList[j]))) { + if (IOwnable(_addressList[j]).owner() == IOwnable(_securityToken).owner() || getBoolValue(Encoder.getKey("verified", _addressList[j]))) { if(_isCompatibleModule(_addressList[j], _securityToken)) { _tempArray[counter] = _addressList[j]; counter ++; } } } - else if (getBool(Encoder.getKey("verified", _addressList[j]))) { + else if (getBoolValue(Encoder.getKey("verified", _addressList[j]))) { if(_isCompatibleModule(_addressList[j], _securityToken)) { _tempArray[counter] = _addressList[j]; counter ++; @@ -370,7 +370,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @notice Stores the contract addresses of other key contracts from the PolymathRegistry */ function updateFromRegistry() external onlyOwner { - address _polymathRegistry = getAddress(Encoder.getKey("polymathRegistry")); + address _polymathRegistry = getAddressValue(Encoder.getKey("polymathRegistry")); set(Encoder.getKey("securityTokenRegistry"), IPolymathRegistry(_polymathRegistry).getAddress("SecurityTokenRegistry")); set(Encoder.getKey("featureRegistry"), IPolymathRegistry(_polymathRegistry).getAddress("FeatureRegistry")); set(Encoder.getKey("polyToken"), IPolymathRegistry(_polymathRegistry).getAddress("PolyToken")); @@ -391,7 +391,7 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return address owner */ function owner() public view returns(address) { - return getAddress(Encoder.getKey("owner")); + return getAddressValue(Encoder.getKey("owner")); } /** @@ -399,6 +399,6 @@ contract ModuleRegistry is IModuleRegistry, EternalStorage { * @return bool */ function isPaused() public view returns(bool) { - return getBool(Encoder.getKey("paused")); + return getBoolValue(Encoder.getKey("paused")); } } diff --git a/contracts/SecurityTokenRegistry.sol b/contracts/SecurityTokenRegistry.sol index d13fbb2b6..81f74abde 100644 --- a/contracts/SecurityTokenRegistry.sol +++ b/contracts/SecurityTokenRegistry.sol @@ -175,7 +175,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { external payable { - require(!getBool(INITIALIZE),"already initialized"); + require(!getBoolValue(INITIALIZE),"already initialized"); require( _STFactory != address(0) && _polyToken != address(0) && _owner != address(0) && _polymathRegistry != address(0), "Invalid address" @@ -210,7 +210,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { // Attempt to charge the reg fee if it is > 0 POLY uint256 tickerFee = getTickerRegistrationFee(); if (tickerFee > 0) - require(IERC20(getAddress(POLYTOKEN)).transferFrom(msg.sender, address(this), tickerFee), "Insufficent allowance"); + require(IERC20(getAddressValue(POLYTOKEN)).transferFrom(msg.sender, address(this), tickerFee), "Insufficent allowance"); string memory ticker = Util.upper(_ticker); require(_tickerAvailable(ticker), "Ticker is reserved"); // Check whether ticker was previously registered (and expired) @@ -286,13 +286,13 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { } // If status is true, there must be a security token linked to the ticker already if (_status) { - require(getAddress(Encoder.getKey("tickerToSecurityToken", _ticker)) != address(0), "Token not registered"); + require(getAddressValue(Encoder.getKey("tickerToSecurityToken", _ticker)) != address(0), "Token not registered"); } _addTicker(_owner, _ticker, _tokenName, _registrationDate, _expiryDate, _status, true, uint256(0)); } function _tickerOwner(string _ticker) internal view returns(address) { - return getAddress(Encoder.getKey("registeredTickers_owner", _ticker)); + return getAddressValue(Encoder.getKey("registeredTickers_owner", _ticker)); } /** @@ -318,7 +318,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { function _tickerAvailable(string _ticker) internal view returns(bool) { if (_tickerOwner(_ticker) != address(0)) { /*solium-disable-next-line security/no-block-members*/ - if ((now > getUint(Encoder.getKey("registeredTickers_expiryDate", _ticker))) && !_tickerStatus(_ticker)) { + if ((now > getUintValue(Encoder.getKey("registeredTickers_expiryDate", _ticker))) && !_tickerStatus(_ticker)) { return true; } else return false; @@ -327,7 +327,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { } function _tickerStatus(string _ticker) internal view returns(bool) { - return getBool(Encoder.getKey("registeredTickers_status", _ticker)); + return getBoolValue(Encoder.getKey("registeredTickers_status", _ticker)); } /** @@ -341,7 +341,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { pushArray(_ownerKey, Util.stringToBytes32(_ticker)); set(Encoder.getKey("tickerIndex", _ticker), length); bytes32 seenKey = Encoder.getKey("seenUsers", _owner); - if (!getBool(seenKey)) { + if (!getBoolValue(seenKey)) { pushArray(Encoder.getKey("activeUsers"), _owner); set(seenKey, true); } @@ -359,19 +359,19 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { bool _status ) internal { bytes32 key = Encoder.getKey("registeredTickers_owner", _ticker); - if (getAddress(key) != _owner) + if (getAddressValue(key) != _owner) set(key, _owner); key = Encoder.getKey("registeredTickers_registrationDate", _ticker); - if (getUint(key) != _registrationDate) + if (getUintValue(key) != _registrationDate) set(key, _registrationDate); key = Encoder.getKey("registeredTickers_expiryDate", _ticker); - if (getUint(key) != _expiryDate) + if (getUintValue(key) != _expiryDate) set(key, _expiryDate); key = Encoder.getKey("registeredTickers_tokenName", _ticker); - if (Encoder.getKey(getString(key)) != Encoder.getKey(_tokenName)) + if (Encoder.getKey(getStringValue(key)) != Encoder.getKey(_tokenName)) set(key, _tokenName); key = Encoder.getKey("registeredTickers_status", _ticker); - if (getBool(key) != _status) + if (getBoolValue(key) != _status) set(key, _status); } @@ -384,9 +384,9 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { string memory ticker = Util.upper(_ticker); require(_newOwner != address(0), "Invalid address"); bytes32 ownerKey = Encoder.getKey("registeredTickers_owner", ticker); - require(getAddress(ownerKey) == msg.sender, "Not authorised"); + require(getAddressValue(ownerKey) == msg.sender, "Not authorised"); if (_tickerStatus(ticker)) - require(IOwnable(getAddress(Encoder.getKey("tickerToSecurityToken", ticker))).owner() == _newOwner, "New owner does not match token owner"); + require(IOwnable(getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker))).owner() == _newOwner, "New owner does not match token owner"); _deleteTickerOwnership(msg.sender, ticker); _setTickerOwnership(_newOwner, ticker); set(ownerKey, _newOwner); @@ -397,7 +397,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @notice Internal - Removes the owner of a ticker */ function _deleteTickerOwnership(address _owner, string _ticker) internal { - uint256 index = uint256(getUint(Encoder.getKey("tickerIndex", _ticker))); + uint256 index = uint256(getUintValue(Encoder.getKey("tickerIndex", _ticker))); bytes32 ownerKey = Encoder.getKey("userToTickers", _owner); bytes32[] memory tickers = getArrayBytes32(ownerKey); assert(index < tickers.length); @@ -415,7 +415,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { */ function changeExpiryLimit(uint256 _newExpiry) external onlyOwner { require(_newExpiry >= 1 days, "Expiry should >= 1 day"); - emit ChangeExpiryLimit(getUint(EXPIRYLIMIT), _newExpiry); + emit ChangeExpiryLimit(getUintValue(EXPIRYLIMIT), _newExpiry); set(EXPIRYLIMIT, _newExpiry); } @@ -430,7 +430,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { for (uint i = 0; i < tickers.length; i++) { string memory ticker = Util.bytes32ToString(tickers[i]); /*solium-disable-next-line security/no-block-members*/ - if (getUint(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || _tickerStatus(ticker)) { + if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || _tickerStatus(ticker)) { counter ++; } } @@ -439,7 +439,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { for (i = 0; i < tickers.length; i++) { ticker = Util.bytes32ToString(tickers[i]); /*solium-disable-next-line security/no-block-members*/ - if (getUint(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || _tickerStatus(ticker)) { + if (getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now || _tickerStatus(ticker)) { tempList[counter] = tickers[i]; counter ++; } @@ -464,7 +464,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { for (i = 0; i < activeUsers.length; i++) { tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); for (j = 0; j < tickers.length; j++) { - token = getAddress(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); + token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); if (token != address(0)) { if (IOwnable(token).owner() == _owner) { count = count + 1; @@ -477,7 +477,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { for (i = 0; i < activeUsers.length; i++) { tickers = getArrayBytes32(Encoder.getKey("userToTickers", activeUsers[i])); for (j = 0; j < tickers.length; j++) { - token = getAddress(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); + token = getAddressValue(Encoder.getKey("tickerToSecurityToken", Util.bytes32ToString(tickers[j]))); if (token != address(0)) { if (IOwnable(token).owner() == _owner) { result[index] = token; @@ -501,15 +501,15 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { function getTickerDetails(string _ticker) external view returns (address, uint256, uint256, string, bool) { string memory ticker = Util.upper(_ticker); bool tickerStatus = _tickerStatus(ticker); - uint256 expiryDate = getUint(Encoder.getKey("registeredTickers_expiryDate", ticker)); + uint256 expiryDate = getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)); /*solium-disable-next-line security/no-block-members*/ if ((tickerStatus == true) || (expiryDate > now)) { return ( _tickerOwner(ticker), - getUint(Encoder.getKey("registeredTickers_registrationDate", ticker)), + getUintValue(Encoder.getKey("registeredTickers_registrationDate", ticker)), expiryDate, - getString(Encoder.getKey("registeredTickers_tokenName", ticker)), + getStringValue(Encoder.getKey("registeredTickers_tokenName", ticker)), tickerStatus ); } else @@ -531,15 +531,15 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { require(bytes(_name).length > 0 && bytes(_ticker).length > 0, "Ticker length > 0"); string memory ticker = Util.upper(_ticker); bytes32 statusKey = Encoder.getKey("registeredTickers_status", ticker); - require(!getBool(statusKey), "Already deployed"); + require(!getBoolValue(statusKey), "Already deployed"); set(statusKey, true); require(_tickerOwner(ticker) == msg.sender, "Not authorised"); /*solium-disable-next-line security/no-block-members*/ - require(getUint(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now, "Ticker gets expired"); + require(getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)) >= now, "Ticker gets expired"); uint256 launchFee = getSecurityTokenLaunchFee(); if (launchFee > 0) - require(IERC20(getAddress(POLYTOKEN)).transferFrom(msg.sender, address(this), launchFee), "Insufficient allowance"); + require(IERC20(getAddressValue(POLYTOKEN)).transferFrom(msg.sender, address(this), launchFee), "Insufficient allowance"); address newSecurityTokenAddress = ISTFactory(getSTFactoryAddress()).deployToken( _name, @@ -548,7 +548,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { _tokenDetails, msg.sender, _divisible, - getAddress(POLYMATHREGISTRY) + getAddressValue(POLYMATHREGISTRY) ); /*solium-disable-next-line security/no-block-members*/ @@ -583,8 +583,8 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { require(_deployedAt != 0 && _owner != address(0), "0 value params not allowed"); string memory ticker = Util.upper(_ticker); require(_securityToken != address(0), "ST address is 0x"); - uint256 registrationTime = getUint(Encoder.getKey("registeredTickers_registrationDate", ticker)); - uint256 expiryTime = getUint(Encoder.getKey("registeredTickers_expiryDate", ticker)); + uint256 registrationTime = getUintValue(Encoder.getKey("registeredTickers_registrationDate", ticker)); + uint256 expiryTime = getUintValue(Encoder.getKey("registeredTickers_expiryDate", ticker)); if (registrationTime == 0) { /*solium-disable-next-line security/no-block-members*/ registrationTime = now; @@ -611,7 +611,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @return bool */ function isSecurityToken(address _securityToken) external view returns (bool) { - return (keccak256(bytes(getString(Encoder.getKey("securityTokens_ticker", _securityToken)))) != keccak256("")); + return (keccak256(bytes(getStringValue(Encoder.getKey("securityTokens_ticker", _securityToken)))) != keccak256("")); } /** @@ -621,7 +621,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { */ function getSecurityTokenAddress(string _ticker) external view returns (address) { string memory ticker = Util.upper(_ticker); - return getAddress(Encoder.getKey("tickerToSecurityToken", ticker)); + return getAddressValue(Encoder.getKey("tickerToSecurityToken", ticker)); } /** @@ -634,10 +634,10 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { */ function getSecurityTokenData(address _securityToken) external view returns (string, address, string, uint256) { return ( - getString(Encoder.getKey("securityTokens_ticker", _securityToken)), + getStringValue(Encoder.getKey("securityTokens_ticker", _securityToken)), IOwnable(_securityToken).owner(), - getString(Encoder.getKey("securityTokens_tokenDetails", _securityToken)), - getUint(Encoder.getKey("securityTokens_deployedAt", _securityToken)) + getStringValue(Encoder.getKey("securityTokens_tokenDetails", _securityToken)), + getUintValue(Encoder.getKey("securityTokens_deployedAt", _securityToken)) ); } @@ -651,7 +651,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { */ function transferOwnership(address _newOwner) external onlyOwner { require(_newOwner != address(0), "Invalid address"); - emit OwnershipTransferred(getAddress(OWNER), _newOwner); + emit OwnershipTransferred(getAddressValue(OWNER), _newOwner); set(OWNER, _newOwner); } @@ -678,7 +678,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @param _tickerRegFee is the registration fee in POLY tokens (base 18 decimals) */ function changeTickerRegistrationFee(uint256 _tickerRegFee) external onlyOwner { - uint256 fee = getUint(TICKERREGFEE); + uint256 fee = getUintValue(TICKERREGFEE); require(fee != _tickerRegFee, "Fee not changed"); emit ChangeTickerRegistrationFee(fee, _tickerRegFee); set(TICKERREGFEE, _tickerRegFee); @@ -689,7 +689,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @param _stLaunchFee is the registration fee in POLY tokens (base 18 decimals) */ function changeSecurityLaunchFee(uint256 _stLaunchFee) external onlyOwner { - uint256 fee = getUint(STLAUNCHFEE); + uint256 fee = getUintValue(STLAUNCHFEE); require(fee != _stLaunchFee, "Fee not changed"); emit ChangeSecurityLaunchFee(fee, _stLaunchFee); set(STLAUNCHFEE, _stLaunchFee); @@ -731,21 +731,21 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { uint24 _packedVersion = VersionUtils.pack(_major, _minor, _patch); require(VersionUtils.isValidVersion(getProtocolVersion(), _version),"In-valid version"); set(Encoder.getKey("latestVersion"), uint256(_packedVersion)); - set(Encoder.getKey("protocolVersionST", getUint(Encoder.getKey("latestVersion"))), _STFactoryAddress); + set(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion"))), _STFactoryAddress); } /** * @notice Returns the current STFactory Address */ function getSTFactoryAddress() public view returns(address) { - return getAddress(Encoder.getKey("protocolVersionST", getUint(Encoder.getKey("latestVersion")))); + return getAddressValue(Encoder.getKey("protocolVersionST", getUintValue(Encoder.getKey("latestVersion")))); } /** * @notice Gets Protocol version */ function getProtocolVersion() public view returns(uint8[]) { - return VersionUtils.unpack(uint24(getUint(Encoder.getKey("latestVersion")))); + return VersionUtils.unpack(uint24(getUintValue(Encoder.getKey("latestVersion")))); } /** @@ -762,7 +762,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @return Fee amount */ function getSecurityTokenLaunchFee() public view returns(uint256) { - return getUint(STLAUNCHFEE); + return getUintValue(STLAUNCHFEE); } /** @@ -770,7 +770,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @return Fee amount */ function getTickerRegistrationFee() public view returns(uint256) { - return getUint(TICKERREGFEE); + return getUintValue(TICKERREGFEE); } /** @@ -778,7 +778,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @return Expiry limit */ function getExpiryLimit() public view returns(uint256) { - return getUint(EXPIRYLIMIT); + return getUintValue(EXPIRYLIMIT); } /** @@ -786,7 +786,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @return bool */ function isPaused() public view returns(bool) { - return getBool(PAUSED); + return getBoolValue(PAUSED); } /** @@ -794,7 +794,7 @@ contract SecurityTokenRegistry is ISecurityTokenRegistry, EternalStorage { * @return address owner */ function owner() public view returns(address) { - return getAddress(OWNER); + return getAddressValue(OWNER); } } diff --git a/contracts/interfaces/IBoot.sol b/contracts/interfaces/IBoot.sol new file mode 100644 index 000000000..db29dc319 --- /dev/null +++ b/contracts/interfaces/IBoot.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.4.24; + +interface IBoot { + + /** + * @notice This function returns the signature of configure function + * @return bytes4 Configure function signature + */ + function getInitFunction() external pure returns(bytes4); +} \ No newline at end of file diff --git a/contracts/interfaces/ISTO.sol b/contracts/interfaces/ISTO.sol new file mode 100644 index 000000000..dd5ce3f16 --- /dev/null +++ b/contracts/interfaces/ISTO.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.24; + +/** + * @title Interface to be implemented by all STO modules + */ +interface ISTO { + /** + * @notice Returns the total no. of tokens sold + */ + function getTokensSold() external view returns (uint256); +} diff --git a/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol b/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol new file mode 100644 index 000000000..29de4d7cd --- /dev/null +++ b/contracts/libraries/BokkyPooBahsDateTimeLibrary.sol @@ -0,0 +1,339 @@ +pragma solidity ^0.4.24; + +// ---------------------------------------------------------------------------- +// BokkyPooBah's DateTime Library v1.00 +// +// A gas-efficient Solidity date and time library +// +// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary +// +// Tested date range 1970/01/01 to 2345/12/31 +// +// Conventions: +// Unit | Range | Notes +// :-------- |:-------------:|:----- +// timestamp | >= 0 | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC +// year | 1970 ... 2345 | +// month | 1 ... 12 | +// day | 1 ... 31 | +// hour | 0 ... 23 | +// minute | 0 ... 59 | +// second | 0 ... 59 | +// dayOfWeek | 1 ... 7 | 1 = Monday, ..., 7 = Sunday +// +// +// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018. +// +// GNU Lesser General Public License 3.0 +// https://www.gnu.org/licenses/lgpl-3.0.en.html +// ---------------------------------------------------------------------------- + +library BokkyPooBahsDateTimeLibrary { + + uint constant SECONDS_PER_DAY = 24 * 60 * 60; + uint constant SECONDS_PER_HOUR = 60 * 60; + uint constant SECONDS_PER_MINUTE = 60; + int constant OFFSET19700101 = 2440588; + + uint constant DOW_MON = 1; + uint constant DOW_TUE = 2; + uint constant DOW_WED = 3; + uint constant DOW_THU = 4; + uint constant DOW_FRI = 5; + uint constant DOW_SAT = 6; + uint constant DOW_SUN = 7; + + // ------------------------------------------------------------------------ + // Calculate the number of days from 1970/01/01 to year/month/day using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and subtracting the offset 2440588 so that 1970/01/01 is day 0 + // + // days = day + // - 32075 + // + 1461 * (year + 4800 + (month - 14) / 12) / 4 + // + 367 * (month - 2 - (month - 14) / 12 * 12) / 12 + // - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4 + // - offset + // ------------------------------------------------------------------------ + function _daysFromDate(uint year, uint month, uint day) internal pure returns (uint _days) { + require(year >= 1970); + int _year = int(year); + int _month = int(month); + int _day = int(day); + + int __days = _day + - 32075 + + 1461 * (_year + 4800 + (_month - 14) / 12) / 4 + + 367 * (_month - 2 - (_month - 14) / 12 * 12) / 12 + - 3 * ((_year + 4900 + (_month - 14) / 12) / 100) / 4 + - OFFSET19700101; + + _days = uint(__days); + } + + // ------------------------------------------------------------------------ + // Calculate year/month/day from the number of days since 1970/01/01 using + // the date conversion algorithm from + // http://aa.usno.navy.mil/faq/docs/JD_Formula.php + // and adding the offset 2440588 so that 1970/01/01 is day 0 + // + // int L = days + 68569 + offset + // int N = 4 * L / 146097 + // L = L - (146097 * N + 3) / 4 + // year = 4000 * (L + 1) / 1461001 + // L = L - 1461 * year / 4 + 31 + // month = 80 * L / 2447 + // dd = L - 2447 * month / 80 + // L = month / 11 + // month = month + 2 - 12 * L + // year = 100 * (N - 49) + year + L + // ------------------------------------------------------------------------ + function _daysToDate(uint _days) internal pure returns (uint year, uint month, uint day) { + int __days = int(_days); + + int L = __days + 68569 + OFFSET19700101; + int N = 4 * L / 146097; + L = L - (146097 * N + 3) / 4; + int _year = 4000 * (L + 1) / 1461001; + L = L - 1461 * _year / 4 + 31; + int _month = 80 * L / 2447; + int _day = L - 2447 * _month / 80; + L = _month / 11; + _month = _month + 2 - 12 * L; + _year = 100 * (N - 49) + _year + L; + + year = uint(_year); + month = uint(_month); + day = uint(_day); + } + + function timestampFromDate(uint year, uint month, uint day) internal pure returns (uint timestamp) { + timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY; + } + function timestampFromDateTime(uint year, uint month, uint day, uint hour, uint minute, uint second) internal pure returns (uint timestamp) { + timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second; + } + function timestampToDate(uint timestamp) internal pure returns (uint year, uint month, uint day) { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function timestampToDateTime(uint timestamp) internal pure returns (uint year, uint month, uint day, uint hour, uint minute, uint second) { + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + secs = secs % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + second = secs % SECONDS_PER_MINUTE; + } + + function isValidDate(uint year, uint month, uint day) internal pure returns (bool valid) { + if (year >= 1970 && month > 0 && month <= 12) { + uint daysInMonth = _getDaysInMonth(year, month); + if (day > 0 && day <= daysInMonth) { + valid = true; + } + } + } + function isValidDateTime(uint year, uint month, uint day, uint hour, uint minute, uint second) internal pure returns (bool valid) { + if (isValidDate(year, month, day)) { + if (hour < 24 && minute < 60 && second < 60) { + valid = true; + } + } + } + function isLeapYear(uint timestamp) internal pure returns (bool leapYear) { + uint year; + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + leapYear = _isLeapYear(year); + } + function _isLeapYear(uint year) internal pure returns (bool leapYear) { + leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); + } + function isWeekDay(uint timestamp) internal pure returns (bool weekDay) { + weekDay = getDayOfWeek(timestamp) <= DOW_FRI; + } + function isWeekEnd(uint timestamp) internal pure returns (bool weekEnd) { + weekEnd = getDayOfWeek(timestamp) >= DOW_SAT; + } + function getDaysInMonth(uint timestamp) internal pure returns (uint daysInMonth) { + uint year; + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + daysInMonth = _getDaysInMonth(year, month); + } + function _getDaysInMonth(uint year, uint month) internal pure returns (uint daysInMonth) { + if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { + daysInMonth = 31; + } else if (month != 2) { + daysInMonth = 30; + } else { + daysInMonth = _isLeapYear(year) ? 29 : 28; + } + } + // 1 = Monday, 7 = Sunday + function getDayOfWeek(uint timestamp) internal pure returns (uint dayOfWeek) { + uint _days = timestamp / SECONDS_PER_DAY; + dayOfWeek = (_days + 3) % 7 + 1; + } + + function getYear(uint timestamp) internal pure returns (uint year) { + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getMonth(uint timestamp) internal pure returns (uint month) { + uint year; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getDay(uint timestamp) internal pure returns (uint day) { + uint year; + uint month; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + } + function getHour(uint timestamp) internal pure returns (uint hour) { + uint secs = timestamp % SECONDS_PER_DAY; + hour = secs / SECONDS_PER_HOUR; + } + function getMinute(uint timestamp) internal pure returns (uint minute) { + uint secs = timestamp % SECONDS_PER_HOUR; + minute = secs / SECONDS_PER_MINUTE; + } + function getSecond(uint timestamp) internal pure returns (uint second) { + second = timestamp % SECONDS_PER_MINUTE; + } + + function addYears(uint timestamp, uint _years) internal pure returns (uint newTimestamp) { + uint year; + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + year += _years; + uint daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; + require(newTimestamp >= timestamp); + } + function addMonths(uint timestamp, uint _months) internal pure returns (uint newTimestamp) { + uint year; + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + month += _months; + year += (month - 1) / 12; + month = (month - 1) % 12 + 1; + uint daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; + require(newTimestamp >= timestamp); + } + function addDays(uint timestamp, uint _days) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp + _days * SECONDS_PER_DAY; + require(newTimestamp >= timestamp); + } + function addHours(uint timestamp, uint _hours) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp + _hours * SECONDS_PER_HOUR; + require(newTimestamp >= timestamp); + } + function addMinutes(uint timestamp, uint _minutes) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE; + require(newTimestamp >= timestamp); + } + function addSeconds(uint timestamp, uint _seconds) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp + _seconds; + require(newTimestamp >= timestamp); + } + + function subYears(uint timestamp, uint _years) internal pure returns (uint newTimestamp) { + uint year; + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + year -= _years; + uint daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; + require(newTimestamp <= timestamp); + } + function subMonths(uint timestamp, uint _months) internal pure returns (uint newTimestamp) { + uint year; + uint month; + uint day; + (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY); + uint yearMonth = year * 12 + (month - 1) - _months; + year = yearMonth / 12; + month = yearMonth % 12 + 1; + uint daysInMonth = _getDaysInMonth(year, month); + if (day > daysInMonth) { + day = daysInMonth; + } + newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + timestamp % SECONDS_PER_DAY; + require(newTimestamp <= timestamp); + } + function subDays(uint timestamp, uint _days) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp - _days * SECONDS_PER_DAY; + require(newTimestamp <= timestamp); + } + function subHours(uint timestamp, uint _hours) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp - _hours * SECONDS_PER_HOUR; + require(newTimestamp <= timestamp); + } + function subMinutes(uint timestamp, uint _minutes) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE; + require(newTimestamp <= timestamp); + } + function subSeconds(uint timestamp, uint _seconds) internal pure returns (uint newTimestamp) { + newTimestamp = timestamp - _seconds; + require(newTimestamp <= timestamp); + } + + function diffYears(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _years) { + require(fromTimestamp <= toTimestamp); + uint fromYear; + uint fromMonth; + uint fromDay; + uint toYear; + uint toMonth; + uint toDay; + (fromYear, fromMonth, fromDay) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (toYear, toMonth, toDay) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + _years = toYear - fromYear; + } + function diffMonths(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _months) { + require(fromTimestamp <= toTimestamp); + uint fromYear; + uint fromMonth; + uint fromDay; + uint toYear; + uint toMonth; + uint toDay; + (fromYear, fromMonth, fromDay) = _daysToDate(fromTimestamp / SECONDS_PER_DAY); + (toYear, toMonth, toDay) = _daysToDate(toTimestamp / SECONDS_PER_DAY); + _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth; + } + function diffDays(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _days) { + require(fromTimestamp <= toTimestamp); + _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY; + } + function diffHours(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _hours) { + require(fromTimestamp <= toTimestamp); + _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR; + } + function diffMinutes(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _minutes) { + require(fromTimestamp <= toTimestamp); + _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE; + } + function diffSeconds(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _seconds) { + require(fromTimestamp <= toTimestamp); + _seconds = toTimestamp - fromTimestamp; + } +} diff --git a/contracts/libraries/VolumeRestrictionLib.sol b/contracts/libraries/VolumeRestrictionLib.sol new file mode 100644 index 000000000..b0962f231 --- /dev/null +++ b/contracts/libraries/VolumeRestrictionLib.sol @@ -0,0 +1,99 @@ +pragma solidity ^0.4.24; + +import "./BokkyPooBahsDateTimeLibrary.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../interfaces/ISecurityToken.sol"; +import "../storage/VolumeRestrictionTMStorage.sol"; + +library VolumeRestrictionLib { + + using SafeMath for uint256; + + function deleteHolderFromList( + VolumeRestrictionTMStorage.RestrictedData storage data, + address _holder, + VolumeRestrictionTMStorage.TypeOfPeriod _typeOfPeriod + ) + public + { + // Deleting the holder if holder's type of Period is `Both` type otherwise + // it will assign the given type `_typeOfPeriod` to the _holder typeOfPeriod + // `_typeOfPeriod` it always be contrary to the removing restriction + // if removing restriction is individual then typeOfPeriod is TypeOfPeriod.OneDay + // in uint8 its value is 1. if removing restriction is daily individual then typeOfPeriod + // is TypeOfPeriod.MultipleDays in uint8 its value is 0. + if (data.restrictedHolders[_holder].typeOfPeriod != VolumeRestrictionTMStorage.TypeOfPeriod.Both) { + uint128 index = data.restrictedHolders[_holder].index; + uint256 _len = data.restrictedAddresses.length; + if (index != _len) { + data.restrictedHolders[data.restrictedAddresses[_len - 1]].index = index; + data.restrictedAddresses[index - 1] = data.restrictedAddresses[_len - 1]; + } + delete data.restrictedHolders[_holder]; + data.restrictedAddresses.length--; + } else { + data.restrictedHolders[_holder].typeOfPeriod = _typeOfPeriod; + } + } + + function addRestrictionData( + VolumeRestrictionTMStorage.RestrictedData storage data, + address _holder, + VolumeRestrictionTMStorage.TypeOfPeriod _callFrom, + uint256 _endTime + ) + public + { + uint128 index = data.restrictedHolders[_holder].index; + if (data.restrictedHolders[_holder].seen == 0) { + data.restrictedAddresses.push(_holder); + index = uint128(data.restrictedAddresses.length); + } + VolumeRestrictionTMStorage.TypeOfPeriod _type = _getTypeOfPeriod(data.restrictedHolders[_holder].typeOfPeriod, _callFrom, _endTime); + data.restrictedHolders[_holder] = VolumeRestrictionTMStorage.RestrictedHolder(uint8(1), _type, index); + } + + function _getTypeOfPeriod( + VolumeRestrictionTMStorage.TypeOfPeriod _currentTypeOfPeriod, + VolumeRestrictionTMStorage.TypeOfPeriod _callFrom, + uint256 _endTime + ) + internal + pure + returns(VolumeRestrictionTMStorage.TypeOfPeriod) + { + if (_currentTypeOfPeriod != _callFrom && _endTime != uint256(0)) + return VolumeRestrictionTMStorage.TypeOfPeriod.Both; + else + return _callFrom; + } + + function isValidAmountAfterRestrictionChanges( + uint256 _amountTradedLastDay, + uint256 _amount, + uint256 _sumOfLastPeriod, + uint256 _allowedAmount, + uint256 _lastTradedTimestamp + ) + public + view + returns(bool) + { + // if restriction is to check whether the current transaction is performed within the 24 hours + // span after the last transaction performed by the user + if (BokkyPooBahsDateTimeLibrary.diffSeconds(_lastTradedTimestamp, now) < 86400) { + (,, uint256 lastTxDay) = BokkyPooBahsDateTimeLibrary.timestampToDate(_lastTradedTimestamp); + (,, uint256 currentTxDay) = BokkyPooBahsDateTimeLibrary.timestampToDate(now); + // This if statement is to check whether the last transaction timestamp (of `individualRestriction[_from]` + // when `_isDefault` is true or defaultRestriction when `_isDefault` is false) is comes within the same day of the current + // transaction timestamp or not. + if (lastTxDay == currentTxDay) { + // Not allow to transact more than the current transaction restriction allowed amount + if ((_sumOfLastPeriod.add(_amount)).add(_amountTradedLastDay) > _allowedAmount) + return false; + } + } + return true; + } + +} diff --git a/contracts/modules/STO/DummySTO.sol b/contracts/mocks/DummySTO.sol similarity index 93% rename from contracts/modules/STO/DummySTO.sol rename to contracts/mocks/DummySTO.sol index 1b44d2e9b..a444df49c 100644 --- a/contracts/modules/STO/DummySTO.sol +++ b/contracts/mocks/DummySTO.sol @@ -1,12 +1,12 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; -import "../../interfaces/ISecurityToken.sol"; +import "../modules/STO/STO.sol"; +import "../interfaces/ISecurityToken.sol"; /** * @title STO module for sample implementation of a different crowdsale module */ -contract DummySTO is ISTO { +contract DummySTO is STO { bytes32 public constant ADMIN = "ADMIN"; @@ -90,4 +90,8 @@ contract DummySTO is ISTO { return allPermissions; } + function () payable { + //Payable fallback function to allow us to test leaking ETH + } + } diff --git a/contracts/modules/STO/DummySTOFactory.sol b/contracts/mocks/DummySTOFactory.sol similarity index 97% rename from contracts/modules/STO/DummySTOFactory.sol rename to contracts/mocks/DummySTOFactory.sol index 04640ac58..03d5fb3a6 100644 --- a/contracts/modules/STO/DummySTOFactory.sol +++ b/contracts/mocks/DummySTOFactory.sol @@ -1,8 +1,8 @@ pragma solidity ^0.4.24; import "./DummySTO.sol"; -import "../ModuleFactory.sol"; -import "../../libraries/Util.sol"; +import "../modules/ModuleFactory.sol"; +import "../libraries/Util.sol"; /** * @title Factory for deploying DummySTO module diff --git a/contracts/mocks/MockFactory.sol b/contracts/mocks/MockFactory.sol index 627efb71c..8be7754f7 100644 --- a/contracts/mocks/MockFactory.sol +++ b/contracts/mocks/MockFactory.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "../modules/STO/DummySTOFactory.sol"; +import "./DummySTOFactory.sol"; /** * @title Mock Contract Not fit for production environment @@ -32,7 +32,7 @@ contract MockFactory is DummySTOFactory { res[1] = 1; return res; } - + } function changeTypes() external onlyOwner { diff --git a/contracts/mocks/PolyTokenFaucet.sol b/contracts/mocks/PolyTokenFaucet.sol index 76f1a5596..f06716c42 100644 --- a/contracts/mocks/PolyTokenFaucet.sol +++ b/contracts/mocks/PolyTokenFaucet.sol @@ -18,8 +18,8 @@ contract PolyTokenFaucet { mapping(address => uint256) balances; mapping(address => mapping(address => uint256)) allowed; - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); constructor() public { decimals = 18; diff --git a/contracts/mocks/TestSTOFactory.sol b/contracts/mocks/TestSTOFactory.sol index a8bcbbeb6..120f72bbf 100644 --- a/contracts/mocks/TestSTOFactory.sol +++ b/contracts/mocks/TestSTOFactory.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "../modules/STO/DummySTOFactory.sol"; +import "./DummySTOFactory.sol"; contract TestSTOFactory is DummySTOFactory { diff --git a/contracts/modules/Checkpoint/DividendCheckpoint.sol b/contracts/modules/Checkpoint/DividendCheckpoint.sol index 452a48b13..626638924 100644 --- a/contracts/modules/Checkpoint/DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/DividendCheckpoint.sol @@ -8,7 +8,9 @@ pragma solidity ^0.4.24; import "./ICheckpoint.sol"; +import "./DividendCheckpointStorage.sol"; import "../Module.sol"; +import "../../Pausable.sol"; import "../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/math/Math.sol"; @@ -17,55 +19,71 @@ import "openzeppelin-solidity/contracts/math/Math.sol"; * @title Checkpoint module for issuing ether dividends * @dev abstract contract */ -contract DividendCheckpoint is ICheckpoint, Module { +contract DividendCheckpoint is DividendCheckpointStorage, ICheckpoint, Module, Pausable { using SafeMath for uint256; - uint256 public EXCLUDED_ADDRESS_LIMIT = 50; - bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; - bytes32 public constant MANAGE = "MANAGE"; - bytes32 public constant CHECKPOINT = "CHECKPOINT"; - - struct Dividend { - uint256 checkpointId; - uint256 created; // Time at which the dividend was created - uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass - uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - - // set to very high value to bypass - uint256 amount; // Dividend amount in WEI - uint256 claimedAmount; // Amount of dividend claimed so far - uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) - bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend - uint256 dividendWithheld; - uint256 dividendWithheldReclaimed; - mapping (address => bool) claimed; // List of addresses which have claimed dividend - mapping (address => bool) dividendExcluded; // List of addresses which cannot claim dividends - bytes32 name; // Name/title - used for identification - } - - // List of all dividends - Dividend[] public dividends; - - // List of addresses which cannot claim dividends - address[] public excluded; - - // Mapping from address to withholding tax as a percentage * 10**16 - mapping (address => uint256) public withholdingTax; - - // Total amount of ETH withheld per investor - mapping (address => uint256) public investorWithheld; - event SetDefaultExcludedAddresses(address[] _excluded, uint256 _timestamp); event SetWithholding(address[] _investors, uint256[] _withholding, uint256 _timestamp); event SetWithholdingFixed(address[] _investors, uint256 _withholding, uint256 _timestamp); + event SetWallet(address indexed _oldWallet, address indexed _newWallet, uint256 _timestamp); + event UpdateDividendDates(uint256 indexed _dividendIndex, uint256 _maturity, uint256 _expiry); modifier validDividendIndex(uint256 _dividendIndex) { + _validDividendIndex(_dividendIndex); + _; + } + + function _validDividendIndex(uint256 _dividendIndex) internal view { require(_dividendIndex < dividends.length, "Invalid dividend"); require(!dividends[_dividendIndex].reclaimed, "Dividend reclaimed"); /*solium-disable-next-line security/no-block-members*/ require(now >= dividends[_dividendIndex].maturity, "Dividend maturity in future"); /*solium-disable-next-line security/no-block-members*/ require(now < dividends[_dividendIndex].expiry, "Dividend expiry in past"); - _; + } + + /** + * @notice Pause (overridden function) + */ + function pause() public onlyOwner { + super._pause(); + } + + /** + * @notice Unpause (overridden function) + */ + function unpause() public onlyOwner { + super._unpause(); + } + + /** + * @notice Reclaims ERC20Basic compatible tokens + * @dev We duplicate here due to the overriden owner & onlyOwner + * @param _tokenContract The address of the token contract + */ + function reclaimERC20(address _tokenContract) external onlyOwner { + require(_tokenContract != address(0), "Invalid address"); + IERC20 token = IERC20(_tokenContract); + uint256 balance = token.balanceOf(address(this)); + require(token.transfer(msg.sender, balance), "Transfer failed"); + } + + /** + * @notice Reclaims ETH + * @dev We duplicate here due to the overriden owner & onlyOwner + */ + function reclaimETH() external onlyOwner { + msg.sender.transfer(address(this).balance); + } + + /** + * @notice Function used to intialize the contract variables + * @param _wallet Ethereum account address to receive reclaimed dividends and tax + */ + function configure( + address _wallet + ) public onlyFactory { + _setWallet(_wallet); } /** @@ -73,7 +91,21 @@ contract DividendCheckpoint is ICheckpoint, Module { * @return bytes4 */ function getInitFunction() public pure returns (bytes4) { - return bytes4(0); + return this.configure.selector; + } + + /** + * @notice Function used to change wallet address + * @param _wallet Ethereum account address to receive reclaimed dividends and tax + */ + function changeWallet(address _wallet) external onlyOwner { + _setWallet(_wallet); + } + + function _setWallet(address _wallet) internal { + require(_wallet != address(0)); + emit SetWallet(wallet, _wallet, now); + wallet = _wallet; } /** @@ -175,7 +207,8 @@ contract DividendCheckpoint is ICheckpoint, Module { validDividendIndex(_dividendIndex) { Dividend storage dividend = dividends[_dividendIndex]; - address[] memory investors = ISecurityToken(securityToken).getInvestors(); + uint256 checkpointId = dividend.checkpointId; + address[] memory investors = ISecurityToken(securityToken).getInvestorsAt(checkpointId); uint256 numberInvestors = Math.min256(investors.length, _start.add(_iterations)); for (uint256 i = _start; i < numberInvestors; i++) { address payee = investors[i]; @@ -189,7 +222,7 @@ contract DividendCheckpoint is ICheckpoint, Module { * @notice Investors can pull their own dividends * @param _dividendIndex Dividend to pull */ - function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) + function pullDividendPayment(uint256 _dividendIndex) public validDividendIndex(_dividendIndex) whenNotPaused { Dividend storage dividend = dividends[_dividendIndex]; require(!dividend.claimed[msg.sender], "Dividend already claimed"); @@ -259,6 +292,161 @@ contract DividendCheckpoint is ICheckpoint, Module { */ function withdrawWithholding(uint256 _dividendIndex) external; + /** + * @notice Allows issuer to change maturity / expiry dates for dividends + * @dev NB - setting the maturity of a currently matured dividend to a future date + * @dev will effectively refreeze claims on that dividend until the new maturity date passes + * @ dev NB - setting the expiry date to a past date will mean no more payments can be pulled + * @dev or pushed out of a dividend + * @param _dividendIndex Dividend to withdraw from + * @param _maturity updated maturity date + * @param _expiry updated expiry date + */ + function updateDividendDates(uint256 _dividendIndex, uint256 _maturity, uint256 _expiry) external onlyOwner { + require(_dividendIndex < dividends.length, "Invalid dividend"); + require(_expiry > _maturity, "Expiry before maturity"); + Dividend storage dividend = dividends[_dividendIndex]; + dividend.expiry = _expiry; + dividend.maturity = _maturity; + emit UpdateDividendDates(_dividendIndex, _maturity, _expiry); + } + + /** + * @notice Get static dividend data + * @return uint256[] timestamp of dividends creation + * @return uint256[] timestamp of dividends maturity + * @return uint256[] timestamp of dividends expiry + * @return uint256[] amount of dividends + * @return uint256[] claimed amount of dividends + * @return bytes32[] name of dividends + */ + function getDividendsData() external view returns ( + uint256[] memory createds, + uint256[] memory maturitys, + uint256[] memory expirys, + uint256[] memory amounts, + uint256[] memory claimedAmounts, + bytes32[] memory names) + { + createds = new uint256[](dividends.length); + maturitys = new uint256[](dividends.length); + expirys = new uint256[](dividends.length); + amounts = new uint256[](dividends.length); + claimedAmounts = new uint256[](dividends.length); + names = new bytes32[](dividends.length); + for (uint256 i = 0; i < dividends.length; i++) { + (createds[i], maturitys[i], expirys[i], amounts[i], claimedAmounts[i], names[i]) = getDividendData(i); + } + } + + /** + * @notice Get static dividend data + * @return uint256 timestamp of dividend creation + * @return uint256 timestamp of dividend maturity + * @return uint256 timestamp of dividend expiry + * @return uint256 amount of dividend + * @return uint256 claimed amount of dividend + * @return bytes32 name of dividend + */ + function getDividendData(uint256 _dividendIndex) public view returns ( + uint256 created, + uint256 maturity, + uint256 expiry, + uint256 amount, + uint256 claimedAmount, + bytes32 name) + { + created = dividends[_dividendIndex].created; + maturity = dividends[_dividendIndex].maturity; + expiry = dividends[_dividendIndex].expiry; + amount = dividends[_dividendIndex].amount; + claimedAmount = dividends[_dividendIndex].claimedAmount; + name = dividends[_dividendIndex].name; + } + + /** + * @notice Retrieves list of investors, their claim status and whether they are excluded + * @param _dividendIndex Dividend to withdraw from + * @return address[] list of investors + * @return bool[] whether investor has claimed + * @return bool[] whether investor is excluded + * @return uint256[] amount of withheld tax (estimate if not claimed) + * @return uint256[] amount of claim (estimate if not claimeed) + * @return uint256[] investor balance + */ + function getDividendProgress(uint256 _dividendIndex) external view returns ( + address[] memory investors, + bool[] memory resultClaimed, + bool[] memory resultExcluded, + uint256[] memory resultWithheld, + uint256[] memory resultAmount, + uint256[] memory resultBalance) + { + require(_dividendIndex < dividends.length, "Invalid dividend"); + //Get list of Investors + Dividend storage dividend = dividends[_dividendIndex]; + uint256 checkpointId = dividend.checkpointId; + investors = ISecurityToken(securityToken).getInvestorsAt(checkpointId); + resultClaimed = new bool[](investors.length); + resultExcluded = new bool[](investors.length); + resultWithheld = new uint256[](investors.length); + resultAmount = new uint256[](investors.length); + resultBalance = new uint256[](investors.length); + for (uint256 i; i < investors.length; i++) { + resultClaimed[i] = dividend.claimed[investors[i]]; + resultExcluded[i] = dividend.dividendExcluded[investors[i]]; + resultBalance[i] = ISecurityToken(securityToken).balanceOfAt(investors[i], dividend.checkpointId); + if (!resultExcluded[i]) { + if (resultClaimed[i]) { + resultWithheld[i] = dividend.withheld[investors[i]]; + resultAmount[i] = resultBalance[i].mul(dividend.amount).div(dividend.totalSupply).sub(resultWithheld[i]); + } else { + (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, investors[i]); + resultWithheld[i] = withheld; + resultAmount[i] = claim.sub(withheld); + } + } + } + } + + /** + * @notice Retrieves list of investors, their balances, and their current withholding tax percentage + * @param _checkpointId Checkpoint Id to query for + * @return address[] list of investors + * @return uint256[] investor balances + * @return uint256[] investor withheld percentages + */ + function getCheckpointData(uint256 _checkpointId) external view returns (address[] memory investors, uint256[] memory balances, uint256[] memory withholdings) { + require(_checkpointId <= ISecurityToken(securityToken).currentCheckpointId(), "Invalid checkpoint"); + investors = ISecurityToken(securityToken).getInvestorsAt(_checkpointId); + balances = new uint256[](investors.length); + withholdings = new uint256[](investors.length); + for (uint256 i; i < investors.length; i++) { + balances[i] = ISecurityToken(securityToken).balanceOfAt(investors[i], _checkpointId); + withholdings[i] = withholdingTax[investors[i]]; + } + } + + /** + * @notice Checks whether an address is excluded from claiming a dividend + * @param _dividendIndex Dividend to withdraw from + * @return bool whether the address is excluded + */ + function isExcluded(address _investor, uint256 _dividendIndex) external view returns (bool) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + return dividends[_dividendIndex].dividendExcluded[_investor]; + } + + /** + * @notice Checks whether an address has claimed a dividend + * @param _dividendIndex Dividend to withdraw from + * @return bool whether the address has claimed + */ + function isClaimed(address _investor, uint256 _dividendIndex) external view returns (bool) { + require(_dividendIndex < dividends.length, "Invalid dividend"); + return dividends[_dividendIndex].claimed[_investor]; + } + /** * @notice Return the permissions flag that are associated with this module * @return bytes32 array diff --git a/contracts/modules/Checkpoint/DividendCheckpointStorage.sol b/contracts/modules/Checkpoint/DividendCheckpointStorage.sol new file mode 100644 index 000000000..2ae0473ac --- /dev/null +++ b/contracts/modules/Checkpoint/DividendCheckpointStorage.sol @@ -0,0 +1,43 @@ +pragma solidity ^0.4.24; + +/** + * @title Holds the storage variable for the DividendCheckpoint modules (i.e ERC20, Ether) + * @dev abstract contract + */ +contract DividendCheckpointStorage { + + // Address to which reclaimed dividends and withholding tax is sent + address public wallet; + uint256 public EXCLUDED_ADDRESS_LIMIT = 150; + bytes32 public constant DISTRIBUTE = "DISTRIBUTE"; + bytes32 public constant MANAGE = "MANAGE"; + bytes32 public constant CHECKPOINT = "CHECKPOINT"; + + struct Dividend { + uint256 checkpointId; + uint256 created; // Time at which the dividend was created + uint256 maturity; // Time after which dividend can be claimed - set to 0 to bypass + uint256 expiry; // Time until which dividend can be claimed - after this time any remaining amount can be withdrawn by issuer - + // set to very high value to bypass + uint256 amount; // Dividend amount in WEI + uint256 claimedAmount; // Amount of dividend claimed so far + uint256 totalSupply; // Total supply at the associated checkpoint (avoids recalculating this) + bool reclaimed; // True if expiry has passed and issuer has reclaimed remaining dividend + uint256 totalWithheld; + uint256 totalWithheldWithdrawn; + mapping (address => bool) claimed; // List of addresses which have claimed dividend + mapping (address => bool) dividendExcluded; // List of addresses which cannot claim dividends + mapping (address => uint256) withheld; // Amount of tax withheld from claim + bytes32 name; // Name/title - used for identification + } + + // List of all dividends + Dividend[] public dividends; + + // List of addresses which cannot claim dividends + address[] public excluded; + + // Mapping from address to withholding tax as a percentage * 10**16 + mapping (address => uint256) public withholdingTax; + +} diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol index 1f7f25cd5..d9dc93579 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpoint.sol @@ -1,17 +1,16 @@ pragma solidity ^0.4.24; import "./DividendCheckpoint.sol"; +import "./ERC20DividendCheckpointStorage.sol"; import "../../interfaces/IOwnable.sol"; import "../../interfaces/IERC20.sol"; /** * @title Checkpoint module for issuing ERC20 dividends */ -contract ERC20DividendCheckpoint is DividendCheckpoint { +contract ERC20DividendCheckpoint is ERC20DividendCheckpointStorage, DividendCheckpoint { using SafeMath for uint256; - // Mapping to token address for each dividend - mapping (uint256 => address) public dividendTokens; event ERC20DividendDeposited( address indexed _depositor, uint256 _checkpointId, @@ -26,20 +25,20 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { ); event ERC20DividendClaimed( address indexed _payee, - uint256 _dividendIndex, + uint256 indexed _dividendIndex, address indexed _token, uint256 _amount, uint256 _withheld ); event ERC20DividendReclaimed( address indexed _claimer, - uint256 _dividendIndex, + uint256 indexed _dividendIndex, address indexed _token, uint256 _claimedAmount ); event ERC20DividendWithholdingWithdrawn( address indexed _claimer, - uint256 _dividendIndex, + uint256 indexed _dividendIndex, address indexed _token, uint256 _withheldAmount ); @@ -68,8 +67,8 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { address _token, uint256 _amount, bytes32 _name - ) - external + ) + external withPerm(MANAGE) { createDividendWithExclusions(_maturity, _expiry, _token, _amount, excluded, _name); @@ -133,16 +132,16 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _name Name/Title for identification */ function createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - uint256 _checkpointId, + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, address[] _excluded, bytes32 _name - ) + ) public - withPerm(MANAGE) + withPerm(MANAGE) { _createDividendWithCheckpointAndExclusions(_maturity, _expiry, _token, _amount, _checkpointId, _excluded, _name); } @@ -158,15 +157,15 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { * @param _name Name/Title for identification */ function _createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - address _token, - uint256 _amount, - uint256 _checkpointId, + uint256 _maturity, + uint256 _expiry, + address _token, + uint256 _amount, + uint256 _checkpointId, address[] _excluded, bytes32 _name - ) - internal + ) + internal { ISecurityToken securityTokenInstance = ISecurityToken(securityToken); require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); @@ -210,7 +209,7 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { } /** - * @notice Emits the ERC20DividendDeposited event. + * @notice Emits the ERC20DividendDeposited event. * Seperated into a different function as a workaround for stack too deep error */ function _emitERC20DividendDepositedEvent( @@ -242,10 +241,12 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { uint256 claimAfterWithheld = claim.sub(withheld); if (claimAfterWithheld > 0) { require(IERC20(dividendTokens[_dividendIndex]).transfer(_payee, claimAfterWithheld), "transfer failed"); - _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); - investorWithheld[_payee] = investorWithheld[_payee].add(withheld); - emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); } + if (withheld > 0) { + _dividend.totalWithheld = _dividend.totalWithheld.add(withheld); + _dividend.withheld[_payee] = withheld; + } + emit ERC20DividendClaimed(_payee, _dividendIndex, dividendTokens[_dividendIndex], claim, withheld); } /** @@ -260,9 +261,8 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { dividends[_dividendIndex].reclaimed = true; Dividend storage dividend = dividends[_dividendIndex]; uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - address owner = IOwnable(securityToken).owner(); - require(IERC20(dividendTokens[_dividendIndex]).transfer(owner, remainingAmount), "transfer failed"); - emit ERC20DividendReclaimed(owner, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); + require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingAmount), "transfer failed"); + emit ERC20DividendReclaimed(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingAmount); } /** @@ -272,11 +272,10 @@ contract ERC20DividendCheckpoint is DividendCheckpoint { function withdrawWithholding(uint256 _dividendIndex) external withPerm(MANAGE) { require(_dividendIndex < dividends.length, "Invalid dividend"); Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); - dividend.dividendWithheldReclaimed = dividend.dividendWithheld; - address owner = IOwnable(securityToken).owner(); - require(IERC20(dividendTokens[_dividendIndex]).transfer(owner, remainingWithheld), "transfer failed"); - emit ERC20DividendWithholdingWithdrawn(owner, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); + uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn); + dividend.totalWithheldWithdrawn = dividend.totalWithheld; + require(IERC20(dividendTokens[_dividendIndex]).transfer(wallet, remainingWithheld), "transfer failed"); + emit ERC20DividendWithholdingWithdrawn(wallet, _dividendIndex, dividendTokens[_dividendIndex], remainingWithheld); } } diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol index 5fd158c26..e18e54e96 100644 --- a/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpointFactory.sol @@ -1,6 +1,8 @@ pragma solidity ^0.4.24; -import "./ERC20DividendCheckpoint.sol"; +import "../../proxy/ERC20DividendCheckpointProxy.sol"; +import "../../libraries/Util.sol"; +import "../../interfaces/IBoot.sol"; import "../ModuleFactory.sol"; /** @@ -8,32 +10,41 @@ import "../ModuleFactory.sol"; */ contract ERC20DividendCheckpointFactory is ModuleFactory { + address public logicContract; + /** * @notice Constructor * @param _polyAddress Address of the polytoken * @param _setupCost Setup cost of the module * @param _usageCost Usage cost of the module * @param _subscriptionCost Subscription cost of the module + * @param _logicContract Contract address that contains the logic related to `description` */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - version = "1.0.0"; + require(_logicContract != address(0), "Invalid logic contract"); + version = "2.1.1"; name = "ERC20DividendCheckpoint"; title = "ERC20 Dividend Checkpoint"; description = "Create ERC20 dividends for token holders at a specific checkpoint"; 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) { + function deploy(bytes _data) external returns(address) { if (setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "insufficent allowance"); - address erc20DividendCheckpoint = new ERC20DividendCheckpoint(msg.sender, address(polyToken)); + address erc20DividendCheckpoint = new ERC20DividendCheckpointProxy(msg.sender, address(polyToken), logicContract); + //Checks that _data is valid (not calling anything it shouldn't) + require(Util.getSig(_data) == IBoot(erc20DividendCheckpoint).getInitFunction(), "Invalid data"); + /*solium-disable-next-line security/no-low-level-calls*/ + require(erc20DividendCheckpoint.call(_data), "Unsuccessfull call"); /*solium-disable-next-line security/no-block-members*/ emit GenerateModuleFromFactory(erc20DividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); return erc20DividendCheckpoint; diff --git a/contracts/modules/Checkpoint/ERC20DividendCheckpointStorage.sol b/contracts/modules/Checkpoint/ERC20DividendCheckpointStorage.sol new file mode 100644 index 000000000..29401f8d9 --- /dev/null +++ b/contracts/modules/Checkpoint/ERC20DividendCheckpointStorage.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.24; + +/** + * @title It holds the storage variables related to ERC20DividendCheckpoint module + */ +contract ERC20DividendCheckpointStorage { + + // Mapping to token address for each dividend + mapping (uint256 => address) public dividendTokens; + +} diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol index 4def51468..699da0b57 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpoint.sol @@ -8,6 +8,7 @@ import "../../interfaces/IOwnable.sol"; */ contract EtherDividendCheckpoint is DividendCheckpoint { using SafeMath for uint256; + event EtherDividendDeposited( address indexed _depositor, uint256 _checkpointId, @@ -16,13 +17,13 @@ contract EtherDividendCheckpoint is DividendCheckpoint { uint256 _expiry, uint256 _amount, uint256 _totalSupply, - uint256 _dividendIndex, + uint256 indexed _dividendIndex, bytes32 indexed _name ); - event EtherDividendClaimed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendReclaimed(address indexed _claimer, uint256 _dividendIndex, uint256 _claimedAmount); - event EtherDividendClaimFailed(address indexed _payee, uint256 _dividendIndex, uint256 _amount, uint256 _withheld); - event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 _dividendIndex, uint256 _withheldAmount); + event EtherDividendClaimed(address indexed _payee, uint256 indexed _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendReclaimed(address indexed _claimer, uint256 indexed _dividendIndex, uint256 _claimedAmount); + event EtherDividendClaimFailed(address indexed _payee, uint256 indexed _dividendIndex, uint256 _amount, uint256 _withheld); + event EtherDividendWithholdingWithdrawn(address indexed _claimer, uint256 indexed _dividendIndex, uint256 _withheldAmount); /** * @notice Constructor @@ -56,7 +57,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { uint256 _expiry, uint256 _checkpointId, bytes32 _name - ) + ) external payable withPerm(MANAGE) @@ -76,7 +77,7 @@ contract EtherDividendCheckpoint is DividendCheckpoint { uint256 _expiry, address[] _excluded, bytes32 _name - ) + ) public payable withPerm(MANAGE) @@ -94,10 +95,10 @@ contract EtherDividendCheckpoint is DividendCheckpoint { * @param _name Name/title for identification */ function createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - uint256 _checkpointId, - address[] _excluded, + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + address[] _excluded, bytes32 _name ) public @@ -116,12 +117,12 @@ contract EtherDividendCheckpoint is DividendCheckpoint { * @param _name Name/title for identification */ function _createDividendWithCheckpointAndExclusions( - uint256 _maturity, - uint256 _expiry, - uint256 _checkpointId, - address[] _excluded, + uint256 _maturity, + uint256 _expiry, + uint256 _checkpointId, + address[] _excluded, bytes32 _name - ) + ) internal { require(_excluded.length <= EXCLUDED_ADDRESS_LIMIT, "Too many addresses excluded"); @@ -169,19 +170,19 @@ contract EtherDividendCheckpoint is DividendCheckpoint { */ function _payDividend(address _payee, Dividend storage _dividend, uint256 _dividendIndex) internal { (uint256 claim, uint256 withheld) = calculateDividend(_dividendIndex, _payee); - _dividend.claimed[_payee] = true; + _dividend.claimed[_payee] = true; uint256 claimAfterWithheld = claim.sub(withheld); - if (claimAfterWithheld > 0) { - /*solium-disable-next-line security/no-send*/ - if (_payee.send(claimAfterWithheld)) { - _dividend.claimedAmount = _dividend.claimedAmount.add(claim); - _dividend.dividendWithheld = _dividend.dividendWithheld.add(withheld); - investorWithheld[_payee] = investorWithheld[_payee].add(withheld); - emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); - } else { - _dividend.claimed[_payee] = false; - emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); + /*solium-disable-next-line security/no-send*/ + if (_payee.send(claimAfterWithheld)) { + _dividend.claimedAmount = _dividend.claimedAmount.add(claim); + if (withheld > 0) { + _dividend.totalWithheld = _dividend.totalWithheld.add(withheld); + _dividend.withheld[_payee] = withheld; } + emit EtherDividendClaimed(_payee, _dividendIndex, claim, withheld); + } else { + _dividend.claimed[_payee] = false; + emit EtherDividendClaimFailed(_payee, _dividendIndex, claim, withheld); } } @@ -197,9 +198,8 @@ contract EtherDividendCheckpoint is DividendCheckpoint { Dividend storage dividend = dividends[_dividendIndex]; dividend.reclaimed = true; uint256 remainingAmount = dividend.amount.sub(dividend.claimedAmount); - address owner = IOwnable(securityToken).owner(); - owner.transfer(remainingAmount); - emit EtherDividendReclaimed(owner, _dividendIndex, remainingAmount); + wallet.transfer(remainingAmount); + emit EtherDividendReclaimed(wallet, _dividendIndex, remainingAmount); } /** @@ -209,11 +209,10 @@ contract EtherDividendCheckpoint is DividendCheckpoint { function withdrawWithholding(uint256 _dividendIndex) external withPerm(MANAGE) { require(_dividendIndex < dividends.length, "Incorrect dividend index"); Dividend storage dividend = dividends[_dividendIndex]; - uint256 remainingWithheld = dividend.dividendWithheld.sub(dividend.dividendWithheldReclaimed); - dividend.dividendWithheldReclaimed = dividend.dividendWithheld; - address owner = IOwnable(securityToken).owner(); - owner.transfer(remainingWithheld); - emit EtherDividendWithholdingWithdrawn(owner, _dividendIndex, remainingWithheld); + uint256 remainingWithheld = dividend.totalWithheld.sub(dividend.totalWithheldWithdrawn); + dividend.totalWithheldWithdrawn = dividend.totalWithheld; + wallet.transfer(remainingWithheld); + emit EtherDividendWithholdingWithdrawn(wallet, _dividendIndex, remainingWithheld); } } diff --git a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol index 315760be1..0d74febf2 100644 --- a/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol +++ b/contracts/modules/Checkpoint/EtherDividendCheckpointFactory.sol @@ -1,6 +1,8 @@ pragma solidity ^0.4.24; -import "./EtherDividendCheckpoint.sol"; +import "../../proxy/EtherDividendCheckpointProxy.sol"; +import "../../libraries/Util.sol"; +import "../../interfaces/IBoot.sol"; import "../ModuleFactory.sol"; /** @@ -8,32 +10,41 @@ import "../ModuleFactory.sol"; */ contract EtherDividendCheckpointFactory is ModuleFactory { + address public logicContract; + /** * @notice Constructor * @param _polyAddress Address of the polytoken * @param _setupCost Setup cost of the module * @param _usageCost Usage cost of the module * @param _subscriptionCost Subscription cost of the module + * @param _logicContract Contract address that contains the logic related to `description` */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - version = "1.0.0"; + require(_logicContract != address(0), "Invalid logic contract"); + version = "2.1.1"; name = "EtherDividendCheckpoint"; title = "Ether Dividend Checkpoint"; description = "Create ETH dividends for token holders at a specific checkpoint"; 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) { + function deploy(bytes _data) external returns(address) { if(setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "Insufficent allowance or balance"); - address ethDividendCheckpoint = new EtherDividendCheckpoint(msg.sender, address(polyToken)); + address ethDividendCheckpoint = new EtherDividendCheckpointProxy(msg.sender, address(polyToken), logicContract); + //Checks that _data is valid (not calling anything it shouldn't) + require(Util.getSig(_data) == IBoot(ethDividendCheckpoint).getInitFunction(), "Invalid data"); + /*solium-disable-next-line security/no-low-level-calls*/ + require(ethDividendCheckpoint.call(_data), "Unsuccessfull call"); /*solium-disable-next-line security/no-block-members*/ emit GenerateModuleFromFactory(ethDividendCheckpoint, getName(), address(this), msg.sender, setupCost, now); return ethDividendCheckpoint; diff --git a/contracts/modules/Experimental/TransferManager/BlacklistTransferManager.sol b/contracts/modules/Experimental/TransferManager/BlacklistTransferManager.sol new file mode 100644 index 000000000..d1e653803 --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/BlacklistTransferManager.sol @@ -0,0 +1,385 @@ +pragma solidity ^0.4.24; + +import "../../TransferManager/ITransferManager.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +/** + * @title Transfer Manager module to automate blacklist and restrict transfers + */ +contract BlacklistTransferManager is ITransferManager { + using SafeMath for uint256; + + bytes32 public constant ADMIN = "ADMIN"; + + struct BlacklistsDetails { + uint256 startTime; + uint256 endTime; + uint256 repeatPeriodTime; + } + + //hold the different blacklist details corresponds to its name + mapping(bytes32 => BlacklistsDetails) public blacklists; + + //hold the different name of blacklist corresponds to a investor + mapping(address => bytes32[]) investorToBlacklist; + + //get list of the addresses for a particular blacklist + mapping(bytes32 => address[]) blacklistToInvestor; + + //mapping use to store the indexes for different blacklist types for a investor + mapping(address => mapping(bytes32 => uint256)) investorToIndex; + + //mapping use to store the indexes for different investor for a blacklist type + mapping(bytes32 => mapping(address => uint256)) blacklistToIndex; + + bytes32[] allBlacklists; + + // Emit when new blacklist type is added + event AddBlacklistType( + uint256 _startTime, + uint256 _endTime, + bytes32 _blacklistName, + uint256 _repeatPeriodTime + ); + + // Emit when there is a change in the blacklist type + event ModifyBlacklistType( + uint256 _startTime, + uint256 _endTime, + bytes32 _blacklistName, + uint256 _repeatPeriodTime + ); + + // Emit when the added blacklist type is deleted + event DeleteBlacklistType( + bytes32 _blacklistName + ); + + // Emit when new investor is added to the blacklist type + event AddInvestorToBlacklist( + address indexed _investor, + bytes32 _blacklistName + ); + + // Emit when investor is deleted from the blacklist type + event DeleteInvestorFromBlacklist( + address indexed _investor, + bytes32 _blacklistName + ); + + + /** + * @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 This function returns the signature of configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + + /** + * @notice Used to verify the transfer transaction + * @param _from Address of the sender + * @dev Restrict the blacklist address to transfer tokens + * if the current time is between the timeframe define for the + * blacklist type associated with the _from address + */ + function verifyTransfer(address _from, address /* _to */, uint256 /* _amount */, bytes /* _data */, bool /* _isTransfer */) public returns(Result) { + if (!paused) { + if (investorToBlacklist[_from].length != 0) { + for (uint256 i = 0; i < investorToBlacklist[_from].length; i++) { + uint256 endTimeTemp = blacklists[investorToBlacklist[_from][i]].endTime; + uint256 startTimeTemp = blacklists[investorToBlacklist[_from][i]].startTime; + uint256 repeatPeriodTimeTemp = blacklists[investorToBlacklist[_from][i]].repeatPeriodTime * 1 days; + /*solium-disable-next-line security/no-block-members*/ + if (now > startTimeTemp) { + // Find the repeating parameter that will be used to calculate the new startTime and endTime + // based on the new current time value + /*solium-disable-next-line security/no-block-members*/ + uint256 repeater = (now.sub(startTimeTemp)).div(repeatPeriodTimeTemp); + /*solium-disable-next-line security/no-block-members*/ + if (startTimeTemp.add(repeatPeriodTimeTemp.mul(repeater)) <= now && endTimeTemp.add(repeatPeriodTimeTemp.mul(repeater)) >= now) { + return Result.INVALID; + } + } + } + } + } + return Result.NA; + } + + /** + * @notice Used to add the blacklist type + * @param _startTime Start date of the blacklist type + * @param _endTime End date of the blacklist type + * @param _blacklistName Name of the blacklist type + * @param _repeatPeriodTime Repeat period of the blacklist type + */ + function addBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) public withPerm(ADMIN) { + _addBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + } + + /** + * @notice Used to add the multiple blacklist type + * @param _startTimes Start date of the blacklist type + * @param _endTimes End date of the blacklist type + * @param _blacklistNames Name of the blacklist type + * @param _repeatPeriodTimes Repeat period of the blacklist type + */ + function addBlacklistTypeMulti(uint256[] _startTimes, uint256[] _endTimes, bytes32[] _blacklistNames, uint256[] _repeatPeriodTimes) external withPerm(ADMIN) { + require (_startTimes.length == _endTimes.length && _endTimes.length == _blacklistNames.length && _blacklistNames.length == _repeatPeriodTimes.length, "Input array's length mismatch"); + for (uint256 i = 0; i < _startTimes.length; i++){ + _addBlacklistType(_startTimes[i], _endTimes[i], _blacklistNames[i], _repeatPeriodTimes[i]); + } + } + + /** + * @notice Internal function + */ + function _validParams(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal view { + require(_blacklistName != bytes32(0), "Invalid blacklist name"); + require(_startTime >= now && _startTime < _endTime, "Invalid start or end date"); + require(_repeatPeriodTime.mul(1 days) >= _endTime.sub(_startTime) || _repeatPeriodTime == 0); + } + + /** + * @notice Used to modify the details of a given blacklist type + * @param _startTime Start date of the blacklist type + * @param _endTime End date of the blacklist type + * @param _blacklistName Name of the blacklist type + * @param _repeatPeriodTime Repeat period of the blacklist type + */ + function modifyBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) public withPerm(ADMIN) { + require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); + _validParams(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + blacklists[_blacklistName] = BlacklistsDetails(_startTime, _endTime, _repeatPeriodTime); + emit ModifyBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + } + + /** + * @notice Used to modify the details of a given multpile blacklist types + * @param _startTimes Start date of the blacklist type + * @param _endTimes End date of the blacklist type + * @param _blacklistNames Name of the blacklist type + * @param _repeatPeriodTimes Repeat period of the blacklist type + */ + function modifyBlacklistTypeMulti(uint256[] _startTimes, uint256[] _endTimes, bytes32[] _blacklistNames, uint256[] _repeatPeriodTimes) external withPerm(ADMIN) { + require (_startTimes.length == _endTimes.length && _endTimes.length == _blacklistNames.length && _blacklistNames.length == _repeatPeriodTimes.length, "Input array's length mismatch"); + for (uint256 i = 0; i < _startTimes.length; i++){ + modifyBlacklistType(_startTimes[i], _endTimes[i], _blacklistNames[i], _repeatPeriodTimes[i]); + } + } + + /** + * @notice Used to delete the blacklist type + * @param _blacklistName Name of the blacklist type + */ + function deleteBlacklistType(bytes32 _blacklistName) public withPerm(ADMIN) { + require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn’t exist"); + require(blacklistToInvestor[_blacklistName].length == 0, "Investors are associated with the blacklist"); + // delete blacklist type + delete(blacklists[_blacklistName]); + uint256 i = 0; + for (i = 0; i < allBlacklists.length; i++) { + if (allBlacklists[i] == _blacklistName) { + break; + } + } + if (i != allBlacklists.length -1) { + allBlacklists[i] = allBlacklists[allBlacklists.length -1]; + } + allBlacklists.length--; + emit DeleteBlacklistType(_blacklistName); + } + + /** + * @notice Used to delete the multiple blacklist type + * @param _blacklistNames Name of the blacklist type + */ + function deleteBlacklistTypeMulti(bytes32[] _blacklistNames) external withPerm(ADMIN) { + for(uint256 i = 0; i < _blacklistNames.length; i++){ + deleteBlacklistType(_blacklistNames[i]); + } + } + + /** + * @notice Used to assign the blacklist type to the investor + * @param _investor Address of the investor + * @param _blacklistName Name of the blacklist + */ + function addInvestorToBlacklist(address _investor, bytes32 _blacklistName) public withPerm(ADMIN) { + require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); + require(_investor != address(0), "Invalid investor address"); + uint256 index = investorToIndex[_investor][_blacklistName]; + if (index < investorToBlacklist[_investor].length) + require(investorToBlacklist[_investor][index] != _blacklistName, "Blacklist already added to investor"); + uint256 investorIndex = investorToBlacklist[_investor].length; + // Add blacklist index to the investor + investorToIndex[_investor][_blacklistName] = investorIndex; + uint256 blacklistIndex = blacklistToInvestor[_blacklistName].length; + // Add investor index to the blacklist + blacklistToIndex[_blacklistName][_investor] = blacklistIndex; + investorToBlacklist[_investor].push(_blacklistName); + blacklistToInvestor[_blacklistName].push(_investor); + emit AddInvestorToBlacklist(_investor, _blacklistName); + } + + /** + * @notice Used to assign the blacklist type to the multiple investor + * @param _investors Address of the investor + * @param _blacklistName Name of the blacklist + */ + function addInvestorToBlacklistMulti(address[] _investors, bytes32 _blacklistName) external withPerm(ADMIN){ + for(uint256 i = 0; i < _investors.length; i++){ + addInvestorToBlacklist(_investors[i], _blacklistName); + } + } + + /** + * @notice Used to assign the multiple blacklist type to the multiple investor + * @param _investors Address of the investor + * @param _blacklistNames Name of the blacklist + */ + function addMultiInvestorToBlacklistMulti(address[] _investors, bytes32[] _blacklistNames) external withPerm(ADMIN){ + require (_investors.length == _blacklistNames.length, "Input array's length mismatch"); + for(uint256 i = 0; i < _investors.length; i++){ + addInvestorToBlacklist(_investors[i], _blacklistNames[i]); + } + } + + /** + * @notice Used to assign the new blacklist type to the investor + * @param _startTime Start date of the blacklist type + * @param _endTime End date of the blacklist type + * @param _blacklistName Name of the blacklist type + * @param _repeatPeriodTime Repeat period of the blacklist type + * @param _investor Address of the investor + */ + function addInvestorToNewBlacklist(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime, address _investor) external withPerm(ADMIN){ + _addBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + addInvestorToBlacklist(_investor, _blacklistName); + } + + /** + * @notice Used to delete the investor from all the associated blacklist types + * @param _investor Address of the investor + */ + function deleteInvestorFromAllBlacklist(address _investor) public withPerm(ADMIN) { + require(_investor != address(0), "Invalid investor address"); + require(investorToBlacklist[_investor].length != 0, "Investor is not associated to any blacklist type"); + uint256 index = investorToBlacklist[_investor].length - 1; + for (uint256 i = index; i >= 0 && i <= index; i--){ + deleteInvestorFromBlacklist(_investor, investorToBlacklist[_investor][i]); + } + } + + /** + * @notice Used to delete the multiple investor from all the associated blacklist types + * @param _investor Address of the investor + */ + function deleteInvestorFromAllBlacklistMulti(address[] _investor) external withPerm(ADMIN) { + for(uint256 i = 0; i < _investor.length; i++){ + deleteInvestorFromAllBlacklist(_investor[i]); + } + } + + /** + * @notice Used to delete the investor from the blacklist + * @param _investor Address of the investor + * @param _blacklistName Name of the blacklist + */ + function deleteInvestorFromBlacklist(address _investor, bytes32 _blacklistName) public withPerm(ADMIN) { + require(_investor != address(0), "Invalid investor address"); + require(_blacklistName != bytes32(0),"Invalid blacklist name"); + require(investorToBlacklist[_investor][investorToIndex[_investor][_blacklistName]] == _blacklistName, "Investor not associated to the blacklist"); + // delete the investor from the blacklist type + uint256 _blacklistIndex = blacklistToIndex[_blacklistName][_investor]; + uint256 _len = blacklistToInvestor[_blacklistName].length; + if ( _blacklistIndex < _len -1) { + blacklistToInvestor[_blacklistName][_blacklistIndex] = blacklistToInvestor[_blacklistName][_len - 1]; + blacklistToIndex[_blacklistName][blacklistToInvestor[_blacklistName][_blacklistIndex]] = _blacklistIndex; + } + blacklistToInvestor[_blacklistName].length--; + // delete the investor index from the blacklist + delete(blacklistToIndex[_blacklistName][_investor]); + // delete the blacklist from the investor + uint256 _investorIndex = investorToIndex[_investor][_blacklistName]; + _len = investorToBlacklist[_investor].length; + if ( _investorIndex < _len -1) { + investorToBlacklist[_investor][_investorIndex] = investorToBlacklist[_investor][_len - 1]; + investorToIndex[_investor][investorToBlacklist[_investor][_investorIndex]] = _investorIndex; + } + investorToBlacklist[_investor].length--; + // delete the blacklist index from the invetsor + delete(investorToIndex[_investor][_blacklistName]); + emit DeleteInvestorFromBlacklist(_investor, _blacklistName); + } + + /** + * @notice Used to delete the multiple investor from the blacklist + * @param _investors address of the investor + * @param _blacklistNames name of the blacklist + */ + function deleteMultiInvestorsFromBlacklistMulti(address[] _investors, bytes32[] _blacklistNames) external withPerm(ADMIN) { + require (_investors.length == _blacklistNames.length, "Input array's length mismatch"); + for(uint256 i = 0; i < _investors.length; i++){ + deleteInvestorFromBlacklist(_investors[i], _blacklistNames[i]); + } + } + + function _addBlacklistType(uint256 _startTime, uint256 _endTime, bytes32 _blacklistName, uint256 _repeatPeriodTime) internal { + require(blacklists[_blacklistName].endTime == 0, "Blacklist type already exist"); + _validParams(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + blacklists[_blacklistName] = BlacklistsDetails(_startTime, _endTime, _repeatPeriodTime); + allBlacklists.push(_blacklistName); + emit AddBlacklistType(_startTime, _endTime, _blacklistName, _repeatPeriodTime); + } + + /** + * @notice get the list of the investors of a blacklist type + * @param _blacklistName Name of the blacklist type + * @return address List of investors associated with the blacklist + */ + function getListOfAddresses(bytes32 _blacklistName) external view returns(address[]) { + require(blacklists[_blacklistName].endTime != 0, "Blacklist type doesn't exist"); + return blacklistToInvestor[_blacklistName]; + } + + /** + * @notice get the list of the investors of a blacklist type + * @param _user Address of the user + * @return bytes32 List of blacklist names associated with the given address + */ + function getBlacklistNamesToUser(address _user) external view returns(bytes32[]) { + return investorToBlacklist[_user]; + } + + /** + * @notice get the list of blacklist names + * @return bytes32 Array of blacklist names + */ + function getAllBlacklists() external view returns(bytes32[]) { + return allBlacklists; + } + + /** + * @notice Return the permissions flag that are associated with blacklist transfer manager + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } +} + + diff --git a/contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol b/contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol new file mode 100644 index 000000000..36dd4304f --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/BlacklistTransferManagerFactory.sol @@ -0,0 +1,70 @@ +pragma solidity ^0.4.24; + +import "./BlacklistTransferManager.sol"; +import "../../ModuleFactory.sol"; +import "../../../libraries/Util.sol"; + +/** + * @title Factory for deploying BlacklistManager module + */ +contract BlacklistTransferManagerFactory is ModuleFactory { + + /** + * @notice Constructor + * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _usageCost Usage cost of the module + * @param _subscriptionCost Subscription cost of the module + */ + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) + { + version = "2.1.0"; + name = "BlacklistTransferManager"; + title = "Blacklist Transfer Manager"; + description = "Automate blacklist to restrict selling"; + compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + } + + /** + * @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), "Failed transferFrom because of sufficent Allowance is not provided"); + address blacklistTransferManager = new BlacklistTransferManager(msg.sender, address(polyToken)); + /*solium-disable-next-line security/no-block-members*/ + emit GenerateModuleFromFactory(address(blacklistTransferManager), getName(), address(this), msg.sender, setupCost, now); + return address(blacklistTransferManager); + } + + /** + * @notice Type of the Module factory + */ + function getTypes() external view returns(uint8[]) { + uint8[] memory res = new uint8[](1); + res[0] = 2; + return res; + } + + /** + * @notice Get the Instructions that helped to used the module + */ + function getInstructions() public view returns(string) { + return "Allows an issuer to blacklist the addresses."; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() public view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](2); + availableTags[0] = "Blacklist"; + availableTags[1] = "Restricted transfer"; + return availableTags; + } + + +} diff --git a/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol new file mode 100644 index 000000000..93f271420 --- /dev/null +++ b/contracts/modules/Experimental/TransferManager/LockUpTransferManager.sol @@ -0,0 +1,666 @@ +pragma solidity ^0.4.24; + +import "../../TransferManager/ITransferManager.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; + +contract LockUpTransferManager is ITransferManager { + + using SafeMath for uint256; + + // permission definition + bytes32 public constant ADMIN = "ADMIN"; + + // a per-user lockup + struct LockUp { + uint256 lockupAmount; // Amount to be locked + uint256 startTime; // when this lockup starts (seconds) + uint256 lockUpPeriodSeconds; // total period of lockup (seconds) + uint256 releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) + } + + // mapping use to store the lockup details corresponds to lockup name + mapping (bytes32 => LockUp) public lockups; + // mapping user addresses to an array of lockups name for that user + mapping (address => bytes32[]) internal userToLockups; + // get list of the addresses for a particular lockupName + mapping (bytes32 => address[]) internal lockupToUsers; + // holds lockup index corresponds to user address. userAddress => lockupName => lockupIndex + mapping (address => mapping(bytes32 => uint256)) internal userToLockupIndex; + // holds the user address index corresponds to the lockup. lockupName => userAddress => userIndex + mapping (bytes32 => mapping(address => uint256)) internal lockupToUserIndex; + + bytes32[] lockupArray; + + event AddLockUpToUser( + address indexed _userAddress, + bytes32 indexed _lockupName + ); + + event RemoveLockUpFromUser( + address indexed _userAddress, + bytes32 indexed _lockupName + ); + + event ModifyLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 indexed _lockupName + ); + + event AddNewLockUpType( + bytes32 indexed _lockupName, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds + ); + + event RemoveLockUpType(bytes32 indexed _lockupName); + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor (address _securityToken, address _polyAddress) + public + Module(_securityToken, _polyAddress) + { + } + + /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred + * @param _from Address of the sender + * @param _amount The amount of tokens to transfer + */ + function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool /*_isTransfer*/) public returns(Result) { + // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user + if (!paused && _from != address(0) && userToLockups[_from].length != 0) { + // check if this transfer is valid + return _checkIfValidTransfer(_from, _amount); + } + return Result.NA; + } + + /** + * @notice Use to add the new lockup type + * @param _lockupAmount Amount of tokens that need to lock. + * @param _startTime When this lockup starts (seconds) + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + * @param _lockupName Name of the lockup + */ + function addNewLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + public + withPerm(ADMIN) + { + _addNewLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + /** + * @notice Use to add the new lockup type + * @param _lockupAmounts Array of amount of tokens that need to lock. + * @param _startTimes Array of startTimes when this lockup starts (seconds) + * @param _lockUpPeriodsSeconds Array of total period of lockup (seconds) + * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) + * @param _lockupNames Array of names of the lockup + */ + function addNewLockUpTypeMulti( + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + bytes32[] _lockupNames + ) + external + withPerm(ADMIN) + { + require( + _lockupNames.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _lockupAmounts.length, + "Input array length mismatch" + ); + for (uint256 i = 0; i < _lockupNames.length; i++) { + _addNewLockUpType( + _lockupAmounts[i], + _startTimes[i], + _lockUpPeriodsSeconds[i], + _releaseFrequenciesSeconds[i], + _lockupNames[i] + ); + } + } + + /** + * @notice Add the lockup to a user + * @param _userAddress Address of the user + * @param _lockupName Name of the lockup + */ + function addLockUpByName( + address _userAddress, + bytes32 _lockupName + ) + public + withPerm(ADMIN) + { + _addLockUpByName(_userAddress, _lockupName); + } + + /** + * @notice Add the lockup to a user + * @param _userAddresses Address of the user + * @param _lockupNames Name of the lockup + */ + function addLockUpByNameMulti( + address[] _userAddresses, + bytes32[] _lockupNames + ) + external + withPerm(ADMIN) + { + require(_userAddresses.length == _lockupNames.length, "Length mismatch"); + for (uint256 i = 0; i < _userAddresses.length; i++) { + _addLockUpByName(_userAddresses[i], _lockupNames[i]); + } + } + + /** + * @notice Lets the admin create a volume restriction lockup for a given address. + * @param _userAddress Address of the user whose tokens should be locked up + * @param _lockupAmount Amount of tokens that need to lock. + * @param _startTime When this lockup starts (seconds) + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + * @param _lockupName Name of the lockup + */ + function addNewLockUpToUser( + address _userAddress, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + external + withPerm(ADMIN) + { + _addNewLockUpToUser( + _userAddress, + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + /** + * @notice Lets the admin create multiple volume restriction lockups for multiple given addresses. + * @param _userAddresses Array of address of the user whose tokens should be locked up + * @param _lockupAmounts Array of the amounts that need to be locked for the different addresses. + * @param _startTimes Array of When this lockup starts (seconds) + * @param _lockUpPeriodsSeconds Array of total periods of lockup (seconds) + * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) + * @param _lockupNames Array of names of the lockup + */ + function addNewLockUpToUserMulti( + address[] _userAddresses, + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + bytes32[] _lockupNames + ) + public + withPerm(ADMIN) + { + require( + _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _userAddresses.length == _lockupAmounts.length && + _userAddresses.length == _lockupNames.length, + "Input array length mismatch" + ); + for (uint256 i = 0; i < _userAddresses.length; i++) { + _addNewLockUpToUser(_userAddresses[i], _lockupAmounts[i], _startTimes[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i], _lockupNames[i]); + } + } + + /** + * @notice Lets the admin remove a user's lock up + * @param _userAddress Address of the user whose tokens are locked up + * @param _lockupName Name of the lockup need to be removed. + */ + function removeLockUpFromUser(address _userAddress, bytes32 _lockupName) external withPerm(ADMIN) { + _removeLockUpFromUser(_userAddress, _lockupName); + } + + /** + * @notice Used to remove the lockup type + * @param _lockupName Name of the lockup + */ + function removeLockupType(bytes32 _lockupName) external withPerm(ADMIN) { + _removeLockupType(_lockupName); + } + + /** + * @notice Used to remove the multiple lockup type + * @param _lockupNames Array of the lockup names. + */ + function removeLockupTypeMulti(bytes32[] _lockupNames) external withPerm(ADMIN) { + for (uint256 i = 0; i < _lockupNames.length; i++) { + _removeLockupType(_lockupNames[i]); + } + } + + /** + * @notice Use to remove the lockup for multiple users + * @param _userAddresses Array of addresses of the user whose tokens are locked up + * @param _lockupNames Array of the names of the lockup that needs to be removed. + */ + function removeLockUpFromUserMulti(address[] _userAddresses, bytes32[] _lockupNames) external withPerm(ADMIN) { + require(_userAddresses.length == _lockupNames.length, "Array length mismatch"); + for (uint256 i = 0; i < _userAddresses.length; i++) { + _removeLockUpFromUser(_userAddresses[i], _lockupNames[i]); + } + } + + /** + * @notice Lets the admin modify a lockup. + * @param _lockupAmount Amount of tokens that needs to be locked + * @param _startTime When this lockup starts (seconds) + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + * @param _lockupName name of the lockup that needs to be modified. + */ + function modifyLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + external + withPerm(ADMIN) + { + _modifyLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + /** + * @notice Lets the admin modify a volume restriction lockup for a multiple address. + * @param _lockupAmounts Array of the amount of tokens that needs to be locked for the respective addresses. + * @param _startTimes Array of the start time of the lockups (seconds) + * @param _lockUpPeriodsSeconds Array of unix timestamp for the list of lockups (seconds). + * @param _releaseFrequenciesSeconds How often to release a tranche of tokens (seconds) + * @param _lockupNames Array of the lockup names that needs to be modified + */ + function modifyLockUpTypeMulti( + uint256[] _lockupAmounts, + uint256[] _startTimes, + uint256[] _lockUpPeriodsSeconds, + uint256[] _releaseFrequenciesSeconds, + bytes32[] _lockupNames + ) + public + withPerm(ADMIN) + { + require( + _lockupNames.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _startTimes.length && /*solium-disable-line operator-whitespace*/ + _lockupNames.length == _lockupAmounts.length, + "Input array length mismatch" + ); + for (uint256 i = 0; i < _lockupNames.length; i++) { + _modifyLockUpType( + _lockupAmounts[i], + _startTimes[i], + _lockUpPeriodsSeconds[i], + _releaseFrequenciesSeconds[i], + _lockupNames[i] + ); + } + } + + /** + * @notice Get a specific element in a user's lockups array given the user's address and the element index + * @param _lockupName The name of the lockup + */ + function getLockUp(bytes32 _lockupName) public view returns ( + uint256 lockupAmount, + uint256 startTime, + uint256 lockUpPeriodSeconds, + uint256 releaseFrequencySeconds, + uint256 unlockedAmount + ) { + if (lockups[_lockupName].lockupAmount != 0) { + return ( + lockups[_lockupName].lockupAmount, + lockups[_lockupName].startTime, + lockups[_lockupName].lockUpPeriodSeconds, + lockups[_lockupName].releaseFrequencySeconds, + _getUnlockedAmountForLockup(_lockupName) + ); + } + return (uint256(0), uint256(0), uint256(0), uint256(0), uint256(0)); + } + + /** + * @notice get the list of the users of a lockup type + * @param _lockupName Name of the lockup type + * @return address List of users associated with the blacklist + */ + function getListOfAddresses(bytes32 _lockupName) external view returns(address[]) { + require(lockups[_lockupName].startTime != 0, "Blacklist type doesn't exist"); + return lockupToUsers[_lockupName]; + } + + /** + * @notice get the list of lockups names + * @return bytes32 Array of lockups names + */ + function getAllLockups() external view returns(bytes32[]) { + return lockupArray; + } + + /** + * @notice Return the data of the lockups + */ + function getAllLockupData() external view returns( + bytes32[] memory, + uint256[] memory, + uint256[] memory, + uint256[] memory, + uint256[] memory, + uint256[] memory + ) + { + uint256[] memory lockupAmounts = new uint256[](lockupArray.length); + uint256[] memory startTimes = new uint256[](lockupArray.length); + uint256[] memory lockUpPeriodSeconds = new uint256[](lockupArray.length); + uint256[] memory releaseFrequencySeconds = new uint256[](lockupArray.length); + uint256[] memory unlockedAmounts = new uint256[](lockupArray.length); + for (uint256 i = 0; i < lockupArray.length; i++) { + (lockupAmounts[i], startTimes[i], lockUpPeriodSeconds[i], releaseFrequencySeconds[i], unlockedAmounts[i]) = getLockUp(lockupArray[i]); + } + return ( + lockupArray, + lockupAmounts, + startTimes, + lockUpPeriodSeconds, + releaseFrequencySeconds, + unlockedAmounts + ); + } + + /** + * @notice get the list of the lockups for a given user + * @param _user Address of the user + * @return bytes32 List of lockups names associated with the given address + */ + function getLockupsNamesToUser(address _user) external view returns(bytes32[]) { + return userToLockups[_user]; + } + + /** + * @notice Use to get the total locked tokens for a given user + * @param _userAddress Address of the user + * @return uint256 Total locked tokens amount + */ + function getLockedTokenToUser(address _userAddress) public view returns(uint256) { + require(_userAddress != address(0), "Invalid address"); + bytes32[] memory userLockupNames = userToLockups[_userAddress]; + uint256 totalRemainingLockedAmount = 0; + + for (uint256 i = 0; i < userLockupNames.length; i++) { + // Find out the remaining locked amount for a given lockup + uint256 remainingLockedAmount = lockups[userLockupNames[i]].lockupAmount.sub(_getUnlockedAmountForLockup(userLockupNames[i])); + // aggregating all the remaining locked amount for all the lockups for a given address + totalRemainingLockedAmount = totalRemainingLockedAmount.add(remainingLockedAmount); + } + return totalRemainingLockedAmount; + } + + /** + * @notice Checks whether the transfer is allowed + * @param _userAddress Address of the user whose lock ups should be checked + * @param _amount Amount of tokens that need to transact + */ + function _checkIfValidTransfer(address _userAddress, uint256 _amount) internal view returns (Result) { + uint256 totalRemainingLockedAmount = getLockedTokenToUser(_userAddress); + // Present balance of the user + uint256 currentBalance = ISecurityToken(securityToken).balanceOf(_userAddress); + if ((currentBalance.sub(_amount)) >= totalRemainingLockedAmount) { + return Result.NA; + } + return Result.INVALID; + } + + /** + * @notice Provide the unlock amount for the given lockup for a particular user + */ + function _getUnlockedAmountForLockup(bytes32 _lockupName) internal view returns (uint256) { + /*solium-disable-next-line security/no-block-members*/ + if (lockups[_lockupName].startTime > now) { + return 0; + } else if (lockups[_lockupName].startTime.add(lockups[_lockupName].lockUpPeriodSeconds) <= now) { + return lockups[_lockupName].lockupAmount; + } else { + // Calculate the no. of periods for a lockup + uint256 noOfPeriods = (lockups[_lockupName].lockUpPeriodSeconds).div(lockups[_lockupName].releaseFrequencySeconds); + // Calculate the transaction time lies in which period + /*solium-disable-next-line security/no-block-members*/ + uint256 elapsedPeriod = (now.sub(lockups[_lockupName].startTime)).div(lockups[_lockupName].releaseFrequencySeconds); + // Find out the unlocked amount for a given lockup + uint256 unLockedAmount = (lockups[_lockupName].lockupAmount.mul(elapsedPeriod)).div(noOfPeriods); + return unLockedAmount; + } + } + + function _removeLockupType(bytes32 _lockupName) internal { + require(lockups[_lockupName].startTime != 0, "Lockup type doesn’t exist"); + require(lockupToUsers[_lockupName].length == 0, "Users are associated with the lockup"); + // delete lockup type + delete(lockups[_lockupName]); + uint256 i = 0; + for (i = 0; i < lockupArray.length; i++) { + if (lockupArray[i] == _lockupName) { + break; + } + } + if (i != lockupArray.length -1) { + lockupArray[i] = lockupArray[lockupArray.length -1]; + } + lockupArray.length--; + emit RemoveLockUpType(_lockupName); + } + + function _modifyLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + /*solium-disable-next-line security/no-block-members*/ + uint256 startTime = _startTime; + + if (_startTime == 0) { + startTime = now; + } + require(startTime >= now, "Invalid start time"); + require(lockups[_lockupName].lockupAmount != 0, "Doesn't exist"); + + _checkLockUpParams( + _lockupAmount, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + + lockups[_lockupName] = LockUp( + _lockupAmount, + startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds + ); + + emit ModifyLockUpType( + _lockupAmount, + startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + } + + function _removeLockUpFromUser(address _userAddress, bytes32 _lockupName) internal { + require(_userAddress != address(0), "Invalid address"); + require(_lockupName != bytes32(0), "Invalid lockup name"); + require( + userToLockups[_userAddress][userToLockupIndex[_userAddress][_lockupName]] == _lockupName, + "User not assosicated with given lockup" + ); + + // delete the user from the lockup type + uint256 _lockupIndex = lockupToUserIndex[_lockupName][_userAddress]; + uint256 _len = lockupToUsers[_lockupName].length; + if ( _lockupIndex != _len) { + lockupToUsers[_lockupName][_lockupIndex] = lockupToUsers[_lockupName][_len - 1]; + lockupToUserIndex[_lockupName][lockupToUsers[_lockupName][_lockupIndex]] = _lockupIndex; + } + lockupToUsers[_lockupName].length--; + // delete the user index from the lockup + delete(lockupToUserIndex[_lockupName][_userAddress]); + // delete the lockup from the user + uint256 _userIndex = userToLockupIndex[_userAddress][_lockupName]; + _len = userToLockups[_userAddress].length; + if ( _userIndex != _len) { + userToLockups[_userAddress][_userIndex] = userToLockups[_userAddress][_len - 1]; + userToLockupIndex[_userAddress][userToLockups[_userAddress][_userIndex]] = _userIndex; + } + userToLockups[_userAddress].length--; + // delete the lockup index from the user + delete(userToLockupIndex[_userAddress][_lockupName]); + emit RemoveLockUpFromUser(_userAddress, _lockupName); + } + + function _addNewLockUpToUser( + address _userAddress, + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + require(_userAddress != address(0), "Invalid address"); + _addNewLockUpType( + _lockupAmount, + _startTime, + _lockUpPeriodSeconds, + _releaseFrequencySeconds, + _lockupName + ); + _addLockUpByName(_userAddress, _lockupName); + } + + function _addLockUpByName( + address _userAddress, + bytes32 _lockupName + ) + internal + { + require(_userAddress != address(0), "Invalid address"); + require(lockups[_lockupName].startTime >= now, "Lockup expired"); + + userToLockupIndex[_userAddress][_lockupName] = userToLockups[_userAddress].length; + lockupToUserIndex[_lockupName][_userAddress] = lockupToUsers[_lockupName].length; + userToLockups[_userAddress].push(_lockupName); + lockupToUsers[_lockupName].push(_userAddress); + emit AddLockUpToUser(_userAddress, _lockupName); + } + + function _addNewLockUpType( + uint256 _lockupAmount, + uint256 _startTime, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds, + bytes32 _lockupName + ) + internal + { + uint256 startTime = _startTime; + require(_lockupName != bytes32(0), "Invalid name"); + require(lockups[_lockupName].lockupAmount == 0, "Already exist"); + /*solium-disable-next-line security/no-block-members*/ + if (_startTime == 0) { + startTime = now; + } + require(startTime >= now, "Invalid start time"); + _checkLockUpParams(_lockupAmount, _lockUpPeriodSeconds, _releaseFrequencySeconds); + lockups[_lockupName] = LockUp(_lockupAmount, startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + lockupArray.push(_lockupName); + emit AddNewLockUpType(_lockupName, _lockupAmount, startTime, _lockUpPeriodSeconds, _releaseFrequencySeconds); + } + + /** + * @notice Parameter checking function for creating or editing a lockup. + * This function will cause an exception if any of the parameters are bad. + * @param _lockupAmount Amount that needs to be locked + * @param _lockUpPeriodSeconds Total period of lockup (seconds) + * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) + */ + function _checkLockUpParams( + uint256 _lockupAmount, + uint256 _lockUpPeriodSeconds, + uint256 _releaseFrequencySeconds + ) + internal + pure + { + require(_lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); + require(_releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); + require(_lockupAmount != 0, "lockupAmount cannot be zero"); + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(0); + } + + /** + * @notice Returns the permissions flag that are associated with Percentage transfer Manager + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } +} diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol b/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol similarity index 74% rename from contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol rename to contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol index 5be77b6e0..22581eba4 100644 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTMFactory.sol +++ b/contracts/modules/Experimental/TransferManager/LockUpTransferManagerFactory.sol @@ -1,12 +1,12 @@ pragma solidity ^0.4.24; -import "./../../ModuleFactory.sol"; -import "./LockupVolumeRestrictionTM.sol"; +import "../../ModuleFactory.sol"; +import "./LockUpTransferManager.sol"; /** - * @title Factory for deploying ManualApprovalTransferManager module + * @title Factory for deploying LockUpTransferManager module */ -contract LockupVolumeRestrictionTMFactory is ModuleFactory { +contract LockUpTransferManagerFactory is ModuleFactory { /** * @notice Constructor @@ -19,8 +19,8 @@ contract LockupVolumeRestrictionTMFactory is ModuleFactory { ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { version = "1.0.0"; - name = "LockupVolumeRestrictionTM"; - title = "Lockup Volume Restriction Transfer Manager"; + name = "LockUpTransferManager"; + title = "LockUp Transfer Manager"; description = "Manage transfers using lock ups over time"; compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); @@ -33,10 +33,10 @@ contract LockupVolumeRestrictionTMFactory is ModuleFactory { function deploy(bytes /* _data */) external returns(address) { if (setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided"); - LockupVolumeRestrictionTM lockupVolumeRestrictionTransferManager = new LockupVolumeRestrictionTM(msg.sender, address(polyToken)); + LockUpTransferManager lockUpTransferManager = new LockUpTransferManager(msg.sender, address(polyToken)); /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(lockupVolumeRestrictionTransferManager), getName(), address(this), msg.sender, now); - return address(lockupVolumeRestrictionTransferManager); + emit GenerateModuleFromFactory(address(lockUpTransferManager), getName(), address(this), msg.sender, setupCost, now); + return address(lockUpTransferManager); } /** @@ -61,7 +61,7 @@ contract LockupVolumeRestrictionTMFactory is ModuleFactory { */ function getTags() external view returns(bytes32[]) { bytes32[] memory availableTags = new bytes32[](2); - availableTags[0] = "Volume"; + availableTags[0] = "LockUp"; availableTags[1] = "Transfer Restriction"; return availableTags; } diff --git a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol deleted file mode 100644 index 80f44cdb6..000000000 --- a/contracts/modules/Experimental/TransferManager/LockupVolumeRestrictionTM.sol +++ /dev/null @@ -1,411 +0,0 @@ -pragma solidity ^0.4.24; - -import "./../../TransferManager/ITransferManager.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; - - -contract LockupVolumeRestrictionTM is ITransferManager { - - using SafeMath for uint256; - - // permission definition - bytes32 public constant ADMIN = "ADMIN"; - - // a per-user lockup - struct LockUp { - uint lockUpPeriodSeconds; // total period of lockup (seconds) - uint releaseFrequencySeconds; // how often to release a tranche of tokens (seconds) - uint startTime; // when this lockup starts (seconds) - uint totalAmount; // total amount of locked up tokens - uint alreadyWithdrawn; // amount already withdrawn for this lockup - } - - // maps user addresses to an array of lockups for that user - mapping (address => LockUp[]) internal lockUps; - - event AddNewLockUp( - address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed addedIndex - ); - - event RemoveLockUp( - address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed removedIndex - ); - - event ModifyLockUp( - address indexed userAddress, - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint indexed modifiedIndex - ); - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - */ - constructor (address _securityToken, address _polyAddress) - public - Module(_securityToken, _polyAddress) - { - } - - - /** @notice Used to verify the transfer transaction and prevent locked up tokens from being transferred - * @param _from Address of the sender - * @param _amount The amount of tokens to transfer - * @param _isTransfer Whether or not this is an actual transfer or just a test to see if the tokens would be transferrable - */ - function verifyTransfer(address _from, address /* _to*/, uint256 _amount, bytes /* _data */, bool _isTransfer) public returns(Result) { - // only attempt to verify the transfer if the token is unpaused, this isn't a mint txn, and there exists a lockup for this user - if (!paused && _from != address(0) && lockUps[_from].length != 0) { - // check if this transfer is valid - return _checkIfValidTransfer(_from, _amount, _isTransfer); - } - return Result.NA; - } - - /** - * @notice Lets the admin create a volume restriction lockup for a given address. - * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockUpPeriodSeconds Total period of lockup (seconds) - * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param _startTime When this lockup starts (seconds) - * @param _totalAmount Total amount of locked up tokens - */ - function addLockUp( - address _userAddress, - uint _lockUpPeriodSeconds, - uint _releaseFrequencySeconds, - uint _startTime, - uint _totalAmount - ) public withPerm(ADMIN) { - uint256 startTime = _startTime; - _checkLockUpParams(_lockUpPeriodSeconds, _releaseFrequencySeconds, _totalAmount); - - // if a startTime of 0 is passed in, then start now. - if (startTime == 0) { - /*solium-disable-next-line security/no-block-members*/ - startTime = now; - } - - lockUps[_userAddress].push(LockUp(_lockUpPeriodSeconds, _releaseFrequencySeconds, startTime, _totalAmount, 0)); - - emit AddNewLockUp( - _userAddress, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - lockUps[_userAddress].length - 1 - ); - } - - /** - * @notice Lets the admin create multiple volume restriction lockups for multiple given addresses. - * @param _userAddresses Array of address of the user whose tokens should be locked up - * @param _lockUpPeriodsSeconds Array of total periods of lockup (seconds) - * @param _releaseFrequenciesSeconds Array of how often to release a tranche of tokens (seconds) - * @param _startTimes Array of When this lockup starts (seconds) - * @param _totalAmounts Array of total amount of locked up tokens - */ - function addLockUpMulti( - address[] _userAddresses, - uint[] _lockUpPeriodsSeconds, - uint[] _releaseFrequenciesSeconds, - uint[] _startTimes, - uint[] _totalAmounts - ) external withPerm(ADMIN) { - require( - _userAddresses.length == _lockUpPeriodsSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _releaseFrequenciesSeconds.length && /*solium-disable-line operator-whitespace*/ - _userAddresses.length == _startTimes.length && - _userAddresses.length == _totalAmounts.length, - "Input array length mismatch" - ); - - for (uint i = 0; i < _userAddresses.length; i++) { - addLockUp(_userAddresses[i], _lockUpPeriodsSeconds[i], _releaseFrequenciesSeconds[i], _startTimes[i], _totalAmounts[i]); - } - - } - - /** - * @notice Lets the admin remove a user's lock up - * @param _userAddress Address of the user whose tokens are locked up - * @param _lockUpIndex The index of the LockUp to remove for the given userAddress - */ - function removeLockUp(address _userAddress, uint _lockUpIndex) public withPerm(ADMIN) { - LockUp[] storage userLockUps = lockUps[_userAddress]; - require(_lockUpIndex < userLockUps.length, "Array out of bounds exception"); - - LockUp memory toRemove = userLockUps[_lockUpIndex]; - - emit RemoveLockUp( - _userAddress, - toRemove.lockUpPeriodSeconds, - toRemove.releaseFrequencySeconds, - toRemove.startTime, - toRemove.totalAmount, - _lockUpIndex - ); - - if (_lockUpIndex < userLockUps.length - 1) { - // move the last element in the array into the index that is desired to be removed. - userLockUps[_lockUpIndex] = userLockUps[userLockUps.length - 1]; - } - // delete the last element - userLockUps.length--; - } - - /** - * @notice Lets the admin modify a volume restriction lockup for a given address. - * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockUpIndex The index of the LockUp to edit for the given userAddress - * @param _lockUpPeriodSeconds Total period of lockup (seconds) - * @param _releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param _startTime When this lockup starts (seconds) - * @param _totalAmount Total amount of locked up tokens - */ - function modifyLockUp( - address _userAddress, - uint _lockUpIndex, - uint _lockUpPeriodSeconds, - uint _releaseFrequencySeconds, - uint _startTime, - uint _totalAmount - ) public withPerm(ADMIN) { - require(_lockUpIndex < lockUps[_userAddress].length, "Array out of bounds exception"); - - uint256 startTime = _startTime; - // if a startTime of 0 is passed in, then start now. - if (startTime == 0) { - /*solium-disable-next-line security/no-block-members*/ - startTime = now; - } - - _checkLockUpParams(_lockUpPeriodSeconds, _releaseFrequencySeconds, _totalAmount); - - // Get the lockup from the master list and edit it - lockUps[_userAddress][_lockUpIndex] = LockUp( - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - lockUps[_userAddress][_lockUpIndex].alreadyWithdrawn - ); - - emit ModifyLockUp( - _userAddress, - _lockUpPeriodSeconds, - _releaseFrequencySeconds, - startTime, - _totalAmount, - _lockUpIndex - ); - } - - /** - * @notice Get the length of the lockups array for a specific user address - * @param _userAddress Address of the user whose tokens should be locked up - */ - function getLockUpsLength(address _userAddress) public view returns (uint) { - return lockUps[_userAddress].length; - } - - /** - * @notice Get a specific element in a user's lockups array given the user's address and the element index - * @param _userAddress Address of the user whose tokens should be locked up - * @param _lockUpIndex The index of the LockUp to edit for the given userAddress - */ - function getLockUp( - address _userAddress, - uint _lockUpIndex) - public view returns ( - uint lockUpPeriodSeconds, - uint releaseFrequencySeconds, - uint startTime, - uint totalAmount, - uint alreadyWithdrawn - ) { - require(_lockUpIndex < lockUps[_userAddress].length, "Array out of bounds exception"); - LockUp storage userLockUp = lockUps[_userAddress][_lockUpIndex]; - return ( - userLockUp.lockUpPeriodSeconds, - userLockUp.releaseFrequencySeconds, - userLockUp.startTime, - userLockUp.totalAmount, - userLockUp.alreadyWithdrawn - ); - } - - /** - * @notice Takes a userAddress as input, and returns a uint that represents the number of tokens allowed to be withdrawn right now - * @param userAddress Address of the user whose lock ups should be checked - */ - function _checkIfValidTransfer(address userAddress, uint amount, bool isTransfer) internal returns (Result) { - // get lock up array for this user - LockUp[] storage userLockUps = lockUps[userAddress]; - - // maps the index of userLockUps to the amount allowed in this transfer - uint[] memory allowedAmountPerLockup = new uint[](userLockUps.length); - - uint[3] memory tokenSums = [ - uint256(0), // allowed amount right now - uint256(0), // total locked up, ever - uint256(0) // already withdrawn, ever - ]; - - // loop over the user's lock ups - for (uint i = 0; i < userLockUps.length; i++) { - LockUp storage aLockUp = userLockUps[i]; - - uint allowedAmountForThisLockup = 0; - - // check if lockup has entirely passed - /*solium-disable-next-line security/no-block-members*/ - if (now >= aLockUp.startTime.add(aLockUp.lockUpPeriodSeconds)) { - // lockup has passed, or not started yet. allow all. - allowedAmountForThisLockup = aLockUp.totalAmount.sub(aLockUp.alreadyWithdrawn); - /*solium-disable-next-line security/no-block-members*/ - } else if (now >= aLockUp.startTime) { - // lockup is active. calculate how many to allow to be withdrawn right now - // calculate how many periods have elapsed already - /*solium-disable-next-line security/no-block-members*/ - uint elapsedPeriods = (now.sub(aLockUp.startTime)).div(aLockUp.releaseFrequencySeconds); - // calculate the total number of periods, overall - uint totalPeriods = aLockUp.lockUpPeriodSeconds.div(aLockUp.releaseFrequencySeconds); - // calculate how much should be released per period - uint amountPerPeriod = aLockUp.totalAmount.div(totalPeriods); - // calculate the number of tokens that should be released, - // multiplied by the number of periods that have elapsed already - // and add it to the total tokenSums[0] - allowedAmountForThisLockup = amountPerPeriod.mul(elapsedPeriods).sub(aLockUp.alreadyWithdrawn); - - } - // tokenSums[0] is allowed sum - tokenSums[0] = tokenSums[0].add(allowedAmountForThisLockup); - // tokenSums[1] is total locked up - tokenSums[1] = tokenSums[1].add(aLockUp.totalAmount); - // tokenSums[2] is total already withdrawn - tokenSums[2] = tokenSums[2].add(aLockUp.alreadyWithdrawn); - - allowedAmountPerLockup[i] = allowedAmountForThisLockup; - } - - // tokenSums[0] is allowed sum - if (amount <= tokenSums[0]) { - // transfer is valid and will succeed. - if (!isTransfer) { - // if this isn't a real transfer, don't subtract the withdrawn amounts from the lockups. it's a "read only" txn - return Result.VALID; - } - - // we are going to write the withdrawn balances back to the lockups, so make sure that the person calling this function is the securityToken itself, since its public - require(msg.sender == securityToken, "Sender is not securityToken"); - - // subtract amounts so they are now known to be withdrawen - for (i = 0; i < userLockUps.length; i++) { - aLockUp = userLockUps[i]; - - // tokenSums[0] is allowed sum - if (allowedAmountPerLockup[i] >= tokenSums[0]) { - aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(tokenSums[0]); - // we withdrew the entire tokenSums[0] from the lockup. We are done. - break; - } else { - // we have to split the tokenSums[0] across mutiple lockUps - aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(allowedAmountPerLockup[i]); - // subtract the amount withdrawn from this lockup - tokenSums[0] = tokenSums[0].sub(allowedAmountPerLockup[i]); - } - - } - return Result.VALID; - } - - return _checkIfUnlockedTokenTransferIsPossible(userAddress, amount, tokenSums[1], tokenSums[2]); - } - - function _checkIfUnlockedTokenTransferIsPossible( - address userAddress, - uint amount, - uint totalSum, - uint alreadyWithdrawnSum - ) internal view returns (Result) { - // the amount the user wants to withdraw is greater than their allowed amounts according to the lockups. however, if the user has like, 10 tokens, but only 4 are locked up, we should let the transfer go through for those 6 that aren't locked up - uint currentUserBalance = ISecurityToken(securityToken).balanceOf(userAddress); - uint stillLockedAmount = totalSum.sub(alreadyWithdrawnSum); - if (currentUserBalance >= stillLockedAmount && amount <= currentUserBalance.sub(stillLockedAmount)) { - // the user has more tokens in their balance than are actually locked up. they should be allowed to withdraw the difference - return Result.VALID; - } - return Result.INVALID; - } - - - /** - * @notice Parameter checking function for creating or editing a lockup. This function will cause an exception if any of the parameters are bad. - * @param lockUpPeriodSeconds Total period of lockup (seconds) - * @param releaseFrequencySeconds How often to release a tranche of tokens (seconds) - * @param totalAmount Total amount of locked up tokens - */ - function _checkLockUpParams(uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint totalAmount) internal view { - require(lockUpPeriodSeconds != 0, "lockUpPeriodSeconds cannot be zero"); - require(releaseFrequencySeconds != 0, "releaseFrequencySeconds cannot be zero"); - require(totalAmount != 0, "totalAmount cannot be zero"); - - // check that the total amount to be released isn't too granular - require( - totalAmount % ISecurityToken(securityToken).granularity() == 0, - "The total amount to be released is more granular than allowed by the token" - ); - - // check that releaseFrequencySeconds evenly divides lockUpPeriodSeconds - require( - lockUpPeriodSeconds % releaseFrequencySeconds == 0, - "lockUpPeriodSeconds must be evenly divisible by releaseFrequencySeconds" - ); - - // check that totalPeriods evenly divides totalAmount - uint totalPeriods = lockUpPeriodSeconds.div(releaseFrequencySeconds); - require( - totalAmount % totalPeriods == 0, - "The total amount being locked up must be evenly divisible by the number of total periods" - ); - - // make sure the amount to be released per period is not too granular for the token - uint amountPerPeriod = totalAmount.div(totalPeriods); - require( - amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, - "The amount to be released per period is more granular than allowed by the token" - ); - } - - /** - * @notice This function returns the signature of configure function - */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(0); - } - - /** - * @notice Returns the permissions flag that are associated with Percentage transfer Manager - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = ADMIN; - return allPermissions; - } -} diff --git a/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTM.sol b/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTM.sol deleted file mode 100644 index b92272167..000000000 --- a/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTM.sol +++ /dev/null @@ -1,331 +0,0 @@ -pragma solidity ^0.4.24; - -import "./../../TransferManager/ITransferManager.sol"; -import "openzeppelin-solidity/contracts/math/SafeMath.sol"; - -/** - * @title Transfer Manager for limiting volume of tokens in a single trade - */ - -contract SingleTradeVolumeRestrictionTM is ITransferManager { - using SafeMath for uint256; - - bytes32 constant public ADMIN = "ADMIN"; - - bool public isTransferLimitInPercentage; - - uint256 public globalTransferLimitInTokens; - - // should be multipled by 10^16. if the transfer percentage is 20%, then globalTransferLimitInPercentage should be 20*10^16 - uint256 public globalTransferLimitInPercentage; - - // Ignore transactions which are part of the primary issuance - bool public allowPrimaryIssuance = true; - - //mapping to store the wallets that are exempted from the volume restriction - mapping(address => bool) public exemptWallets; - - //addresses on this list have special transfer restrictions apart from global - mapping(address => uint) public specialTransferLimitsInTokens; - - mapping(address => uint) public specialTransferLimitsInPercentages; - - event ExemptWalletAdded(address _wallet); - event ExemptWalletRemoved(address _wallet); - event TransferLimitInTokensSet(address _wallet, uint256 _amount); - event TransferLimitInPercentageSet(address _wallet, uint _percentage); - event TransferLimitInPercentageRemoved(address _wallet); - event TransferLimitInTokensRemoved(address _wallet); - event GlobalTransferLimitInTokensSet(uint256 _amount, uint256 _oldAmount); - event GlobalTransferLimitInPercentageSet(uint256 _percentage, uint256 _oldPercentage); - event TransferLimitChangedToTokens(); - event TransferLimitChangedtoPercentage(); - event SetAllowPrimaryIssuance(bool _allowPrimaryIssuance, uint256 _timestamp); - - /** - * @notice Constructor - * @param _securityToken Address of the security token - * @param _polyAddress Address of the polytoken - */ - constructor(address _securityToken, address _polyAddress) public - Module(_securityToken, _polyAddress) - { - - } - - /** @notice Used to verify the transfer transaction and prevent an account from sending more tokens than allowed in a single transfer - * @param _from Address of the sender - * @param _amount The amount of tokens to transfer - */ - function verifyTransfer( - address _from, - address /* _to */, - uint256 _amount, - bytes /* _data */, - bool /* _isTransfer */ - ) - public - returns(Result) - { - bool validTransfer; - - if (exemptWallets[_from] || paused) return Result.NA; - - if (_from == address(0) && allowPrimaryIssuance) { - return Result.NA; - } - - if (isTransferLimitInPercentage) { - if(specialTransferLimitsInPercentages[_from] > 0) { - validTransfer = (_amount.mul(10**18).div(ISecurityToken(securityToken).totalSupply())) <= specialTransferLimitsInPercentages[_from]; - } else { - validTransfer = (_amount.mul(10**18).div(ISecurityToken(securityToken).totalSupply())) <= globalTransferLimitInPercentage; - } - } else { - if (specialTransferLimitsInTokens[_from] > 0) { - validTransfer = _amount <= specialTransferLimitsInTokens[_from]; - } else { - validTransfer = _amount <= globalTransferLimitInTokens; - } - } - if (validTransfer) return Result.NA; - return Result.INVALID; - } - - /** - * @notice Used to intialize the variables of the contract - * @param _isTransferLimitInPercentage true if the transfer limit is in percentage else false - * @param _globalTransferLimitInPercentageOrToken transfer limit per single transaction. - */ - function configure( - bool _isTransferLimitInPercentage, - uint256 _globalTransferLimitInPercentageOrToken, - bool _allowPrimaryIssuance - ) public onlyFactory { - isTransferLimitInPercentage = _isTransferLimitInPercentage; - if (isTransferLimitInPercentage) { - changeGlobalLimitInPercentage(_globalTransferLimitInPercentageOrToken); - } else { - changeGlobalLimitInTokens(_globalTransferLimitInPercentageOrToken); - } - allowPrimaryIssuance = _allowPrimaryIssuance; - } - - /** - * @notice Sets whether or not to consider primary issuance transfers - * @param _allowPrimaryIssuance whether to allow all primary issuance transfers - */ - function setAllowPrimaryIssuance(bool _allowPrimaryIssuance) public withPerm(ADMIN) { - require(_allowPrimaryIssuance != allowPrimaryIssuance, "Must change setting"); - allowPrimaryIssuance = _allowPrimaryIssuance; - /*solium-disable-next-line security/no-block-members*/ - emit SetAllowPrimaryIssuance(_allowPrimaryIssuance, now); - } - - /** - * @notice Changes the manager to use transfer limit as Percentages - * @param _newGlobalTransferLimitInPercentage uint256 new global Transfer Limit In Percentage. - * @dev specialTransferLimits set for wallets have to re-configured - */ - function changeTransferLimitToPercentage(uint256 _newGlobalTransferLimitInPercentage) public withPerm(ADMIN) { - require(!isTransferLimitInPercentage, "Transfer limit already in percentage"); - isTransferLimitInPercentage = true; - changeGlobalLimitInPercentage(_newGlobalTransferLimitInPercentage); - emit TransferLimitChangedtoPercentage(); - } - - /** - * @notice Changes the manager to use transfer limit as tokens - * @param _newGlobalTransferLimit uint256 new global Transfer Limit in tokens. - * @dev specialTransferLimits set for wallets have to re-configured - */ - function changeTransferLimitToTokens(uint _newGlobalTransferLimit) public withPerm(ADMIN) { - require(isTransferLimitInPercentage, "Transfer limit already in tokens"); - isTransferLimitInPercentage = false; - changeGlobalLimitInTokens(_newGlobalTransferLimit); - emit TransferLimitChangedToTokens(); - } - /** - * @notice Changes the global transfer limit - * @param _newGlobalTransferLimitInTokens new transfer limit in tokens - * @dev This function can be used only when The manager is configured to use limits in tokens - */ - function changeGlobalLimitInTokens(uint256 _newGlobalTransferLimitInTokens) public withPerm(ADMIN) { - require(!isTransferLimitInPercentage, "Transfer limit not set in tokens"); - require(_newGlobalTransferLimitInTokens > 0, "Transfer limit has to greater than zero"); - emit GlobalTransferLimitInTokensSet(_newGlobalTransferLimitInTokens, globalTransferLimitInTokens); - globalTransferLimitInTokens = _newGlobalTransferLimitInTokens; - - } - - /** - * @notice Changes the global transfer limit - * @param _newGlobalTransferLimitInPercentage new transfer limit in percentage. - * Multiply the percentage by 10^16. Eg 22% will be 22*10^16 - * @dev This function can be used only when The manager is configured to use limits in percentage - */ - function changeGlobalLimitInPercentage(uint256 _newGlobalTransferLimitInPercentage) public withPerm(ADMIN) { - require(isTransferLimitInPercentage, "Transfer limit not set in Percentage"); - require(_newGlobalTransferLimitInPercentage > 0 && _newGlobalTransferLimitInPercentage <= 100 * 10 ** 16, "Limit not within [0,100]"); - emit GlobalTransferLimitInPercentageSet(_newGlobalTransferLimitInPercentage, globalTransferLimitInPercentage); - globalTransferLimitInPercentage = _newGlobalTransferLimitInPercentage; - - } - - /** - * @notice Adds an exempt wallet - * @param _wallet exempt wallet address - */ - function addExemptWallet(address _wallet) public withPerm(ADMIN) { - require(_wallet != address(0), "Wallet address cannot be a zero address"); - exemptWallets[_wallet] = true; - emit ExemptWalletAdded(_wallet); - } - - /** - * @notice Removes an exempt wallet - * @param _wallet exempt wallet address - */ - function removeExemptWallet(address _wallet) public withPerm(ADMIN) { - require(_wallet != address(0), "Wallet address cannot be a zero address"); - exemptWallets[_wallet] = false; - emit ExemptWalletRemoved(_wallet); - } - - /** - * @notice Adds an array of exempt wallet - * @param _wallets array of exempt wallet addresses - */ - function addExemptWalletMulti(address[] _wallets) public withPerm(ADMIN) { - require(_wallets.length > 0, "Wallets cannot be empty"); - for (uint256 i = 0; i < _wallets.length; i++) { - addExemptWallet(_wallets[i]); - } - } - - /** - * @notice Removes an array of exempt wallet - * @param _wallets array of exempt wallet addresses - */ - function removeExemptWalletMulti(address[] _wallets) public withPerm(ADMIN) { - require(_wallets.length > 0, "Wallets cannot be empty"); - for (uint256 i = 0; i < _wallets.length; i++) { - removeExemptWallet(_wallets[i]); - } - } - - /** - * @notice Sets transfer limit per wallet - * @param _wallet wallet address - * @param _transferLimit transfer limit for the wallet in tokens - * @dev the manager has to be configured to use limits in tokens - */ - function setTransferLimitInTokens(address _wallet, uint _transferLimit) public withPerm(ADMIN) { - require(_transferLimit > 0, "Transfer limit has to be greater than 0"); - require(!isTransferLimitInPercentage, "Transfer limit not in token amount"); - specialTransferLimitsInTokens[_wallet] = _transferLimit; - emit TransferLimitInTokensSet(_wallet, _transferLimit); - } - - /** - * @notice Sets transfer limit for a wallet - * @param _wallet wallet address - * @param _transferLimitInPercentage transfer limit for the wallet in percentage. - * Multiply the percentage by 10^16. Eg 22% will be 22*10^16 - * @dev The manager has to be configured to use percentages - */ - function setTransferLimitInPercentage(address _wallet, uint _transferLimitInPercentage) public withPerm(ADMIN) { - require(isTransferLimitInPercentage, "Transfer limit not in percentage"); - require(_transferLimitInPercentage > 0 && _transferLimitInPercentage <= 100 * 10 ** 16, "Transfer limit not in required range"); - specialTransferLimitsInPercentages[_wallet] = _transferLimitInPercentage; - emit TransferLimitInPercentageSet(_wallet, _transferLimitInPercentage); - } - - - /** - * @notice Removes transfer limit set in percentage for a wallet - * @param _wallet wallet address - */ - function removeTransferLimitInPercentage(address _wallet) public withPerm(ADMIN) { - require(specialTransferLimitsInPercentages[_wallet] > 0, "Wallet Address does not have a transfer limit"); - specialTransferLimitsInPercentages[_wallet] = 0; - emit TransferLimitInPercentageRemoved(_wallet); - } - - /** - * @notice Removes transfer limit set in tokens for a wallet - * @param _wallet wallet address - */ - function removeTransferLimitInTokens(address _wallet) public withPerm(ADMIN) { - require(specialTransferLimitsInTokens[_wallet] > 0, "Wallet Address does not have a transfer limit"); - specialTransferLimitsInTokens[_wallet] = 0; - emit TransferLimitInTokensRemoved(_wallet); - } - - /** - * @notice Sets transfer limits for an array of wallet - * @param _wallets array of wallet addresses - * @param _transferLimits array of transfer limits for each wallet in tokens - * @dev The manager has to be configured to use tokens as limit - */ - function setTransferLimitInTokensMulti(address[] _wallets, uint[] _transferLimits) public withPerm(ADMIN) { - require(_wallets.length > 0, "Wallets cannot be empty"); - require(_wallets.length == _transferLimits.length, "Wallets don't match to transfer limits"); - for (uint256 i = 0; i < _wallets.length; i++ ) { - setTransferLimitInTokens(_wallets[i], _transferLimits[i]); - } - } - - /** - * @notice Sets transfer limits for an array of wallet - * @param _wallets array of wallet addresses - * @param _transferLimitsInPercentage array of transfer limits for each wallet in percentages - * The percentage has to be multipled by 10 ** 16. Eg: 20% would be 20 * 10 ** 16 - * @dev The manager has to be configured to use percentage as limit - */ - function setTransferLimitInPercentageMulti(address[] _wallets, uint[] _transferLimitsInPercentage) public withPerm(ADMIN) { - require(_wallets.length > 0, "Wallets cannot be empty"); - require(_wallets.length == _transferLimitsInPercentage.length, "Wallets don't match to percentage limits"); - for (uint256 i = 0; i < _wallets.length; i++) { - setTransferLimitInPercentage(_wallets[i], _transferLimitsInPercentage[i]); - } - } - - /** - * @notice Removes transfer limits set in tokens for an array of wallet - * @param _wallets array of wallet addresses - */ - function removeTransferLimitInTokensMulti(address[] _wallets) public withPerm(ADMIN) { - require(_wallets.length > 0, "Wallets cannot be empty"); - for (uint i = 0; i < _wallets.length; i++) { - removeTransferLimitInTokens(_wallets[i]); - } - } - - /** - * @notice Removes transfer limits set in percentage for an array of wallet - * @param _wallets array of wallet addresses - */ - function removeTransferLimitInPercentageMulti(address[] _wallets) public withPerm(ADMIN) { - require(_wallets.length > 0, "Wallets cannot be empty"); - for (uint i = 0; i < _wallets.length; i++) { - removeTransferLimitInPercentage(_wallets[i]); - } - } - - /** - * @notice This function returns the signature of configure function - */ - function getInitFunction() public pure returns (bytes4) { - return bytes4(keccak256("configure(bool,uint256,bool)")); - } - - /** - * @notice Returns the permissions flag that are associated with SingleTradeVolumeRestrictionManager - */ - function getPermissions() public view returns(bytes32[]) { - bytes32[] memory allPermissions = new bytes32[](1); - allPermissions[0] = ADMIN; - return allPermissions; - } -} diff --git a/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol b/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol deleted file mode 100644 index e6d8ed2be..000000000 --- a/contracts/modules/Experimental/TransferManager/SingleTradeVolumeRestrictionTMFactory.sol +++ /dev/null @@ -1,79 +0,0 @@ -pragma solidity ^0.4.24; - -import "./../../ModuleFactory.sol"; -import "./SingleTradeVolumeRestrictionTM.sol"; -import "../../../libraries/Util.sol"; - -/** - * @title Factory for deploying SingleTradeVolumeRestrictionManager - */ -contract SingleTradeVolumeRestrictionTMFactory is ModuleFactory { - - - /** - * @notice Constructor - * @param _polyAddress Address of the polytoken - * @param _setupCost Setup cost of the module - * @param _usageCost Usage cost of the module - * @param _subscriptionCost Subscription cost of the module - */ - constructor(address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public - ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) - { - version = "1.0.0"; - name = "SingleTradeVolumeRestrictionTM"; - title = "Single Trade Volume Restriction Manager"; - description = "Imposes volume restriction on a single trade"; - compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); - } - - /** - * @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), "Failed transferFrom because of sufficent Allowance is not provided"); - SingleTradeVolumeRestrictionTM singleTradeVolumeRestrictionManager = new SingleTradeVolumeRestrictionTM(msg.sender, address(polyToken)); - - require(Util.getSig(_data) == singleTradeVolumeRestrictionManager.getInitFunction(), "Provided data is not valid"); - /*solium-disable-next-line security/no-low-level-calls*/ - require(address(singleTradeVolumeRestrictionManager).call(_data), "Unsuccessful call"); - /*solium-disable-next-line security/no-block-members*/ - emit GenerateModuleFromFactory(address(singleTradeVolumeRestrictionManager), getName(), address(this), msg.sender, setupCost, now); - return address(singleTradeVolumeRestrictionManager); - } - - /** - * @notice Get the types of the Module factory - * @return uint8[] - */ - function getTypes() external view returns(uint8[]) { - uint8[] memory res = new uint8[](1); - res[0] = 2; - return res; - } - - /** - * @notice Get the Instructions that help to use the module - * @return string - */ - function getInstructions() external view returns(string) { - /*solium-disable-next-line max-len*/ - return "Allows an issuer to impose volume restriction on a single trade. Init function takes two parameters. First parameter is a bool indicating if restriction is in percentage. The second parameter is the value in percentage or amount of tokens"; - } - - /** - * @notice Get the tags related to the module factory - * @return bytes32[] - */ - function getTags() external view returns(bytes32[]) { - bytes32[] memory availableTags = new bytes32[](3); - availableTags[0] = "Single Trade"; - availableTags[1] = "Transfer"; - availableTags[2] = "Volume"; - return availableTags; - } - -} diff --git a/contracts/modules/Experimental/Wallet/IWallet.sol b/contracts/modules/Experimental/Wallet/IWallet.sol new file mode 100644 index 000000000..96affd472 --- /dev/null +++ b/contracts/modules/Experimental/Wallet/IWallet.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.4.24; + +import "../../../Pausable.sol"; +import "../../Module.sol"; + +/** + * @title Interface to be implemented by all Wallet modules + * @dev abstract contract + */ +contract IWallet is Module, Pausable { + + function unpause() public onlyOwner { + super._unpause(); + } + + function pause() public onlyOwner { + super._pause(); + } +} diff --git a/contracts/modules/Experimental/Wallet/VestingEscrowWallet.sol b/contracts/modules/Experimental/Wallet/VestingEscrowWallet.sol new file mode 100644 index 000000000..c53e1143f --- /dev/null +++ b/contracts/modules/Experimental/Wallet/VestingEscrowWallet.sol @@ -0,0 +1,558 @@ +pragma solidity ^0.4.24; + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../../storage/VestingEscrowWalletStorage.sol"; +import "./IWallet.sol"; + +/** + * @title Wallet for core vesting escrow functionality + */ +contract VestingEscrowWallet is VestingEscrowWalletStorage, IWallet { + using SafeMath for uint256; + + bytes32 public constant ADMIN = "ADMIN"; + + // States used to represent the status of the schedule + enum State {CREATED, STARTED, COMPLETED} + + // Emit when new schedule is added + event AddSchedule( + address indexed _beneficiary, + bytes32 _templateName, + uint256 _startTime + ); + // Emit when schedule is modified + event ModifySchedule( + address indexed _beneficiary, + bytes32 _templateName, + uint256 _startTime + ); + // Emit when all schedules are revoked for user + event RevokeAllSchedules(address indexed _beneficiary); + // Emit when schedule is revoked + event RevokeSchedule(address indexed _beneficiary, bytes32 _templateName); + // Emit when tokes are deposited to wallet + event DepositTokens(uint256 _numberOfTokens, address _sender); + // Emit when all unassigned tokens are sent to treasury + event SendToTreasury(uint256 _numberOfTokens, address _sender); + // Emit when is sent tokes to user + event SendTokens(address indexed _beneficiary, uint256 _numberOfTokens); + // Emit when template is added + event AddTemplate(bytes32 _name, uint256 _numberOfTokens, uint256 _duration, uint256 _frequency); + // Emit when template is removed + event RemoveTemplate(bytes32 _name); + // Emit when the treasury wallet gets changed + event TreasuryWalletChanged(address _newWallet, address _oldWallet); + + /** + * @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 This function returns the signature of the configure function + */ + function getInitFunction() public pure returns (bytes4) { + return bytes4(keccak256("configure(address)")); + } + + /** + * @notice Used to initialize the treasury wallet address + * @param _treasuryWallet Address of the treasury wallet + */ + function configure(address _treasuryWallet) public onlyFactory { + require(_treasuryWallet != address(0), "Invalid address"); + treasuryWallet = _treasuryWallet; + } + + /** + * @notice Used to change the treasury wallet address + * @param _newTreasuryWallet Address of the treasury wallet + */ + function changeTreasuryWallet(address _newTreasuryWallet) public onlyOwner { + require(_newTreasuryWallet != address(0)); + emit TreasuryWalletChanged(_newTreasuryWallet, treasuryWallet); + treasuryWallet = _newTreasuryWallet; + } + + /** + * @notice Used to deposit tokens from treasury wallet to the vesting escrow wallet + * @param _numberOfTokens Number of tokens that should be deposited + */ + function depositTokens(uint256 _numberOfTokens) external withPerm(ADMIN) { + _depositTokens(_numberOfTokens); + } + + function _depositTokens(uint256 _numberOfTokens) internal { + require(_numberOfTokens > 0, "Should be > 0"); + require( + ISecurityToken(securityToken).transferFrom(msg.sender, address(this), _numberOfTokens), + "Failed transferFrom due to insufficent Allowance provided" + ); + unassignedTokens = unassignedTokens.add(_numberOfTokens); + emit DepositTokens(_numberOfTokens, msg.sender); + } + + /** + * @notice Sends unassigned tokens to the treasury wallet + * @param _amount Amount of tokens that should be send to the treasury wallet + */ + function sendToTreasury(uint256 _amount) external withPerm(ADMIN) { + require(_amount > 0, "Amount cannot be zero"); + require(_amount <= unassignedTokens, "Amount is greater than unassigned tokens"); + uint256 amount = unassignedTokens; + unassignedTokens = 0; + require(ISecurityToken(securityToken).transfer(treasuryWallet, amount), "Transfer failed"); + emit SendToTreasury(amount, msg.sender); + } + + /** + * @notice Pushes available tokens to the beneficiary's address + * @param _beneficiary Address of the beneficiary who will receive tokens + */ + function pushAvailableTokens(address _beneficiary) public withPerm(ADMIN) { + _sendTokens(_beneficiary); + } + + /** + * @notice Used to withdraw available tokens by beneficiary + */ + function pullAvailableTokens() external { + _sendTokens(msg.sender); + } + + /** + * @notice Adds template that can be used for creating schedule + * @param _name Name of the template will be created + * @param _numberOfTokens Number of tokens that should be assigned to schedule + * @param _duration Duration of the vesting schedule + * @param _frequency Frequency of the vesting schedule + */ + function addTemplate(bytes32 _name, uint256 _numberOfTokens, uint256 _duration, uint256 _frequency) external withPerm(ADMIN) { + _addTemplate(_name, _numberOfTokens, _duration, _frequency); + } + + function _addTemplate(bytes32 _name, uint256 _numberOfTokens, uint256 _duration, uint256 _frequency) internal { + require(_name != bytes32(0), "Invalid name"); + require(!_isTemplateExists(_name), "Already exists"); + _validateTemplate(_numberOfTokens, _duration, _frequency); + templateNames.push(_name); + templates[_name] = Template(_numberOfTokens, _duration, _frequency, templateNames.length - 1); + emit AddTemplate(_name, _numberOfTokens, _duration, _frequency); + } + + /** + * @notice Removes template with a given name + * @param _name Name of the template that will be removed + */ + function removeTemplate(bytes32 _name) external withPerm(ADMIN) { + require(_isTemplateExists(_name), "Template not found"); + require(templateToUsers[_name].length == 0, "Template is used"); + uint256 index = templates[_name].index; + if (index != templateNames.length - 1) { + templateNames[index] = templateNames[templateNames.length - 1]; + templates[templateNames[index]].index = index; + } + templateNames.length--; + // delete template data + delete templates[_name]; + emit RemoveTemplate(_name); + } + + /** + * @notice Returns count of the templates those can be used for creating schedule + * @return Count of the templates + */ + function getTemplateCount() external view returns(uint256) { + return templateNames.length; + } + + /** + * @notice Gets the list of the template names those can be used for creating schedule + * @return bytes32 Array of all template names were created + */ + function getAllTemplateNames() external view returns(bytes32[]) { + return templateNames; + } + + /** + * @notice Adds vesting schedules for each of the beneficiary's address + * @param _beneficiary Address of the beneficiary for whom it is scheduled + * @param _templateName Name of the template that will be created + * @param _numberOfTokens Total number of tokens for created schedule + * @param _duration Duration of the created vesting schedule + * @param _frequency Frequency of the created vesting schedule + * @param _startTime Start time of the created vesting schedule + */ + function addSchedule( + address _beneficiary, + bytes32 _templateName, + uint256 _numberOfTokens, + uint256 _duration, + uint256 _frequency, + uint256 _startTime + ) + external + withPerm(ADMIN) + { + _addSchedule(_beneficiary, _templateName, _numberOfTokens, _duration, _frequency, _startTime); + } + + function _addSchedule( + address _beneficiary, + bytes32 _templateName, + uint256 _numberOfTokens, + uint256 _duration, + uint256 _frequency, + uint256 _startTime + ) + internal + { + _addTemplate(_templateName, _numberOfTokens, _duration, _frequency); + _addScheduleFromTemplate(_beneficiary, _templateName, _startTime); + } + + /** + * @notice Adds vesting schedules from template for the beneficiary + * @param _beneficiary Address of the beneficiary for whom it is scheduled + * @param _templateName Name of the exists template + * @param _startTime Start time of the created vesting schedule + */ + function addScheduleFromTemplate(address _beneficiary, bytes32 _templateName, uint256 _startTime) external withPerm(ADMIN) { + _addScheduleFromTemplate(_beneficiary, _templateName, _startTime); + } + + function _addScheduleFromTemplate(address _beneficiary, bytes32 _templateName, uint256 _startTime) internal { + require(_beneficiary != address(0), "Invalid address"); + require(_isTemplateExists(_templateName), "Template not found"); + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + require( + schedules[_beneficiary].length == 0 || + schedules[_beneficiary][index].templateName != _templateName, + "Already added" + ); + require(_startTime >= now, "Date in the past"); + uint256 numberOfTokens = templates[_templateName].numberOfTokens; + if (numberOfTokens > unassignedTokens) { + _depositTokens(numberOfTokens.sub(unassignedTokens)); + } + unassignedTokens = unassignedTokens.sub(numberOfTokens); + if (!beneficiaryAdded[_beneficiary]) { + beneficiaries.push(_beneficiary); + beneficiaryAdded[_beneficiary] = true; + } + schedules[_beneficiary].push(Schedule(_templateName, 0, _startTime)); + userToTemplates[_beneficiary].push(_templateName); + userToTemplateIndex[_beneficiary][_templateName] = schedules[_beneficiary].length - 1; + templateToUsers[_templateName].push(_beneficiary); + templateToUserIndex[_templateName][_beneficiary] = templateToUsers[_templateName].length - 1; + emit AddSchedule(_beneficiary, _templateName, _startTime); + } + + /** + * @notice Modifies vesting schedules for each of the beneficiary + * @param _beneficiary Address of the beneficiary for whom it is modified + * @param _templateName Name of the template was used for schedule creation + * @param _startTime Start time of the created vesting schedule + */ + function modifySchedule(address _beneficiary, bytes32 _templateName, uint256 _startTime) public withPerm(ADMIN) { + _modifySchedule(_beneficiary, _templateName, _startTime); + } + + function _modifySchedule(address _beneficiary, bytes32 _templateName, uint256 _startTime) internal { + _checkSchedule(_beneficiary, _templateName); + require(_startTime > now, "Date in the past"); + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + Schedule storage schedule = schedules[_beneficiary][index]; + /*solium-disable-next-line security/no-block-members*/ + require(now < schedule.startTime, "Schedule started"); + schedule.startTime = _startTime; + emit ModifySchedule(_beneficiary, _templateName, _startTime); + } + + /** + * @notice Revokes vesting schedule with given template name for given beneficiary + * @param _beneficiary Address of the beneficiary for whom it is revoked + * @param _templateName Name of the template was used for schedule creation + */ + function revokeSchedule(address _beneficiary, bytes32 _templateName) external withPerm(ADMIN) { + _checkSchedule(_beneficiary, _templateName); + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + _sendTokensPerSchedule(_beneficiary, index); + uint256 releasedTokens = _getReleasedTokens(_beneficiary, index); + unassignedTokens = unassignedTokens.add(templates[_templateName].numberOfTokens.sub(releasedTokens)); + _deleteUserToTemplates(_beneficiary, _templateName); + _deleteTemplateToUsers(_beneficiary, _templateName); + emit RevokeSchedule(_beneficiary, _templateName); + } + + function _deleteUserToTemplates(address _beneficiary, bytes32 _templateName) internal { + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + Schedule[] storage userSchedules = schedules[_beneficiary]; + if (index != userSchedules.length - 1) { + userSchedules[index] = userSchedules[userSchedules.length - 1]; + userToTemplates[_beneficiary][index] = userToTemplates[_beneficiary][userToTemplates[_beneficiary].length - 1]; + userToTemplateIndex[_beneficiary][userSchedules[index].templateName] = index; + } + userSchedules.length--; + userToTemplates[_beneficiary].length--; + delete userToTemplateIndex[_beneficiary][_templateName]; + } + + function _deleteTemplateToUsers(address _beneficiary, bytes32 _templateName) internal { + uint256 templateIndex = templateToUserIndex[_templateName][_beneficiary]; + if (templateIndex != templateToUsers[_templateName].length - 1) { + templateToUsers[_templateName][templateIndex] = templateToUsers[_templateName][templateToUsers[_templateName].length - 1]; + templateToUserIndex[_templateName][templateToUsers[_templateName][templateIndex]] = templateIndex; + } + templateToUsers[_templateName].length--; + delete templateToUserIndex[_templateName][_beneficiary]; + } + + /** + * @notice Revokes all vesting schedules for given beneficiary's address + * @param _beneficiary Address of the beneficiary for whom all schedules will be revoked + */ + function revokeAllSchedules(address _beneficiary) public withPerm(ADMIN) { + _revokeAllSchedules(_beneficiary); + } + + function _revokeAllSchedules(address _beneficiary) internal { + require(_beneficiary != address(0), "Invalid address"); + _sendTokens(_beneficiary); + Schedule[] storage userSchedules = schedules[_beneficiary]; + for (uint256 i = 0; i < userSchedules.length; i++) { + uint256 releasedTokens = _getReleasedTokens(_beneficiary, i); + Template memory template = templates[userSchedules[i].templateName]; + unassignedTokens = unassignedTokens.add(template.numberOfTokens.sub(releasedTokens)); + delete userToTemplateIndex[_beneficiary][userSchedules[i].templateName]; + _deleteTemplateToUsers(_beneficiary, userSchedules[i].templateName); + } + delete schedules[_beneficiary]; + delete userToTemplates[_beneficiary]; + emit RevokeAllSchedules(_beneficiary); + } + + /** + * @notice Returns beneficiary's schedule created using template name + * @param _beneficiary Address of the beneficiary who will receive tokens + * @param _templateName Name of the template was used for schedule creation + * @return beneficiary's schedule data (numberOfTokens, duration, frequency, startTime, claimedTokens, State) + */ + function getSchedule(address _beneficiary, bytes32 _templateName) external view returns(uint256, uint256, uint256, uint256, uint256, State) { + _checkSchedule(_beneficiary, _templateName); + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + Schedule memory schedule = schedules[_beneficiary][index]; + return ( + templates[schedule.templateName].numberOfTokens, + templates[schedule.templateName].duration, + templates[schedule.templateName].frequency, + schedule.startTime, + schedule.claimedTokens, + _getScheduleState(_beneficiary, _templateName) + ); + } + + function _getScheduleState(address _beneficiary, bytes32 _templateName) internal view returns(State) { + _checkSchedule(_beneficiary, _templateName); + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + Schedule memory schedule = schedules[_beneficiary][index]; + if (now < schedule.startTime) { + return State.CREATED; + } else if (now > schedule.startTime && now < schedule.startTime.add(templates[_templateName].duration)) { + return State.STARTED; + } else { + return State.COMPLETED; + } + } + + /** + * @notice Returns list of the template names for given beneficiary's address + * @param _beneficiary Address of the beneficiary + * @return List of the template names that were used for schedule creation + */ + function getTemplateNames(address _beneficiary) external view returns(bytes32[]) { + require(_beneficiary != address(0), "Invalid address"); + return userToTemplates[_beneficiary]; + } + + /** + * @notice Returns count of the schedules were created for given beneficiary + * @param _beneficiary Address of the beneficiary + * @return Count of beneficiary's schedules + */ + function getScheduleCount(address _beneficiary) external view returns(uint256) { + require(_beneficiary != address(0), "Invalid address"); + return schedules[_beneficiary].length; + } + + function _getAvailableTokens(address _beneficiary, uint256 _index) internal view returns(uint256) { + Schedule memory schedule = schedules[_beneficiary][_index]; + uint256 releasedTokens = _getReleasedTokens(_beneficiary, _index); + return releasedTokens.sub(schedule.claimedTokens); + } + + function _getReleasedTokens(address _beneficiary, uint256 _index) internal view returns(uint256) { + Schedule memory schedule = schedules[_beneficiary][_index]; + Template memory template = templates[schedule.templateName]; + /*solium-disable-next-line security/no-block-members*/ + if (now > schedule.startTime) { + uint256 periodCount = template.duration.div(template.frequency); + /*solium-disable-next-line security/no-block-members*/ + uint256 periodNumber = (now.sub(schedule.startTime)).div(template.frequency); + if (periodNumber > periodCount) { + periodNumber = periodCount; + } + return template.numberOfTokens.mul(periodNumber).div(periodCount); + } else { + return 0; + } + } + + /** + * @notice Used to bulk send available tokens for each of the beneficiaries + * @param _fromIndex Start index of array of beneficiary's addresses + * @param _toIndex End index of array of beneficiary's addresses + */ + function pushAvailableTokensMulti(uint256 _fromIndex, uint256 _toIndex) external withPerm(ADMIN) { + require(_toIndex <= beneficiaries.length - 1, "Array out of bound"); + for (uint256 i = _fromIndex; i <= _toIndex; i++) { + if (schedules[beneficiaries[i]].length !=0) + pushAvailableTokens(beneficiaries[i]); + } + } + + /** + * @notice Used to bulk add vesting schedules for each of beneficiary + * @param _beneficiaries Array of the beneficiary's addresses + * @param _templateNames Array of the template names + * @param _numberOfTokens Array of number of tokens should be assigned to schedules + * @param _durations Array of the vesting duration + * @param _frequencies Array of the vesting frequency + * @param _startTimes Array of the vesting start time + */ + function addScheduleMulti( + address[] _beneficiaries, + bytes32[] _templateNames, + uint256[] _numberOfTokens, + uint256[] _durations, + uint256[] _frequencies, + uint256[] _startTimes + ) + public + withPerm(ADMIN) + { + require( + _beneficiaries.length == _templateNames.length && /*solium-disable-line operator-whitespace*/ + _beneficiaries.length == _numberOfTokens.length && /*solium-disable-line operator-whitespace*/ + _beneficiaries.length == _durations.length && /*solium-disable-line operator-whitespace*/ + _beneficiaries.length == _frequencies.length && /*solium-disable-line operator-whitespace*/ + _beneficiaries.length == _startTimes.length, + "Arrays sizes mismatch" + ); + for (uint256 i = 0; i < _beneficiaries.length; i++) { + _addSchedule(_beneficiaries[i], _templateNames[i], _numberOfTokens[i], _durations[i], _frequencies[i], _startTimes[i]); + } + } + + /** + * @notice Used to bulk add vesting schedules from template for each of the beneficiary + * @param _beneficiaries Array of beneficiary's addresses + * @param _templateNames Array of the template names were used for schedule creation + * @param _startTimes Array of the vesting start time + */ + function addScheduleFromTemplateMulti(address[] _beneficiaries, bytes32[] _templateNames, uint256[] _startTimes) external withPerm(ADMIN) { + require(_beneficiaries.length == _templateNames.length && _beneficiaries.length == _startTimes.length, "Arrays sizes mismatch"); + for (uint256 i = 0; i < _beneficiaries.length; i++) { + _addScheduleFromTemplate(_beneficiaries[i], _templateNames[i], _startTimes[i]); + } + } + + /** + * @notice Used to bulk revoke vesting schedules for each of the beneficiaries + * @param _beneficiaries Array of the beneficiary's addresses + */ + function revokeSchedulesMulti(address[] _beneficiaries) external withPerm(ADMIN) { + for (uint256 i = 0; i < _beneficiaries.length; i++) { + _revokeAllSchedules(_beneficiaries[i]); + } + } + + /** + * @notice Used to bulk modify vesting schedules for each of the beneficiaries + * @param _beneficiaries Array of the beneficiary's addresses + * @param _templateNames Array of the template names + * @param _startTimes Array of the vesting start time + */ + function modifyScheduleMulti( + address[] _beneficiaries, + bytes32[] _templateNames, + uint256[] _startTimes + ) + public + withPerm(ADMIN) + { + require( + _beneficiaries.length == _templateNames.length && /*solium-disable-line operator-whitespace*/ + _beneficiaries.length == _startTimes.length, + "Arrays sizes mismatch" + ); + for (uint256 i = 0; i < _beneficiaries.length; i++) { + _modifySchedule(_beneficiaries[i], _templateNames[i], _startTimes[i]); + } + } + + function _checkSchedule(address _beneficiary, bytes32 _templateName) internal view { + require(_beneficiary != address(0), "Invalid address"); + uint256 index = userToTemplateIndex[_beneficiary][_templateName]; + require( + index < schedules[_beneficiary].length && + schedules[_beneficiary][index].templateName == _templateName, + "Schedule not found" + ); + } + + function _isTemplateExists(bytes32 _name) internal view returns(bool) { + return templates[_name].numberOfTokens > 0; + } + + function _validateTemplate(uint256 _numberOfTokens, uint256 _duration, uint256 _frequency) internal view { + require(_numberOfTokens > 0, "Zero amount"); + require(_duration % _frequency == 0, "Invalid frequency"); + uint256 periodCount = _duration.div(_frequency); + require(_numberOfTokens % periodCount == 0); + uint256 amountPerPeriod = _numberOfTokens.div(periodCount); + require(amountPerPeriod % ISecurityToken(securityToken).granularity() == 0, "Invalid granularity"); + } + + function _sendTokens(address _beneficiary) internal { + for (uint256 i = 0; i < schedules[_beneficiary].length; i++) { + _sendTokensPerSchedule(_beneficiary, i); + } + } + + function _sendTokensPerSchedule(address _beneficiary, uint256 _index) internal { + uint256 amount = _getAvailableTokens(_beneficiary, _index); + if (amount > 0) { + schedules[_beneficiary][_index].claimedTokens = schedules[_beneficiary][_index].claimedTokens.add(amount); + require(ISecurityToken(securityToken).transfer(_beneficiary, amount), "Transfer failed"); + emit SendTokens(_beneficiary, amount); + } + } + + /** + * @notice Return the permissions flag that are associated with VestingEscrowWallet + */ + function getPermissions() public view returns(bytes32[]) { + bytes32[] memory allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + return allPermissions; + } + +} diff --git a/contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol b/contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol new file mode 100644 index 000000000..2e35453f0 --- /dev/null +++ b/contracts/modules/Experimental/Wallet/VestingEscrowWalletFactory.sol @@ -0,0 +1,76 @@ +pragma solidity ^0.4.24; + +import "../../../proxy/VestingEscrowWalletProxy.sol"; +import "../../../interfaces/IBoot.sol"; +import "../../ModuleFactory.sol"; +import "../../../libraries/Util.sol"; + +/** + * @title Factory for deploying VestingEscrowWallet module + */ +contract VestingEscrowWalletFactory 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 = "VestingEscrowWallet"; + title = "Vesting Escrow Wallet"; + description = "Manage vesting schedules to employees / affiliates"; + 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 + * _data Data used for the intialization of the module factory variables + * @return address Contract address of the Module + */ + function deploy(bytes _data) external returns(address) { + if (setupCost > 0) { + require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom due to insufficent Allowance provided"); + } + VestingEscrowWalletProxy vestingEscrowWallet = new VestingEscrowWalletProxy(msg.sender, address(polyToken), logicContract); + //Checks that _data is valid (not calling anything it shouldn't) + require(Util.getSig(_data) == IBoot(vestingEscrowWallet).getInitFunction(), "Invalid data"); + /*solium-disable-next-line security/no-low-level-calls*/ + require(address(vestingEscrowWallet).call(_data), "Unsuccessfull call"); + /*solium-disable-next-line security/no-block-members*/ + emit GenerateModuleFromFactory(address(vestingEscrowWallet), getName(), address(this), msg.sender, setupCost, now); + return address(vestingEscrowWallet); + } + + /** + * @notice Type of the Module factory + */ + function getTypes() external view returns(uint8[]) { + uint8[] memory res = new uint8[](1); + res[0] = 6; + return res; + } + + /** + * @notice Returns the instructions associated with the module + */ + function getInstructions() external view returns(string) { + /*solium-disable-next-line max-len*/ + return "Issuer can deposit tokens to the contract and create the vesting schedule for the given address (Affiliate/Employee). These address can withdraw tokens according to there vesting schedule."; + } + + /** + * @notice Get the tags related to the module factory + */ + function getTags() external view returns(bytes32[]) { + bytes32[] memory availableTags = new bytes32[](2); + availableTags[0] = "Vested"; + availableTags[1] = "Escrow Wallet"; + return availableTags; + } +} diff --git a/contracts/modules/Module.sol b/contracts/modules/Module.sol index aa3e6d865..a3698f22f 100644 --- a/contracts/modules/Module.sol +++ b/contracts/modules/Module.sol @@ -2,32 +2,23 @@ pragma solidity ^0.4.24; import "../interfaces/IModule.sol"; import "../interfaces/ISecurityToken.sol"; -import "../interfaces/IERC20.sol"; +import "./ModuleStorage.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; /** * @title Interface that any module contract should implement * @notice Contract is abstract */ -contract Module is IModule { - - address public factory; - - address public securityToken; - - bytes32 public constant FEE_ADMIN = "FEE_ADMIN"; - - IERC20 public polyToken; +contract Module is IModule, ModuleStorage { /** * @notice Constructor * @param _securityToken Address of the security token * @param _polyAddress Address of the polytoken */ - constructor (address _securityToken, address _polyAddress) public { - securityToken = _securityToken; - factory = msg.sender; - polyToken = IERC20(_polyAddress); + constructor (address _securityToken, address _polyAddress) public + ModuleStorage(_securityToken, _polyAddress) + { } //Allows owner, factory or permissioned delegate @@ -65,4 +56,5 @@ contract Module is IModule { require(polyToken.transferFrom(securityToken, Ownable(factory).owner(), _amount), "Unable to take fee"); return true; } + } diff --git a/contracts/modules/ModuleFactory.sol b/contracts/modules/ModuleFactory.sol index aca262621..9fc6cea02 100644 --- a/contracts/modules/ModuleFactory.sol +++ b/contracts/modules/ModuleFactory.sol @@ -28,18 +28,6 @@ contract ModuleFactory is IModuleFactory, Ownable { // @dev uint24 consists packed value of uint8 _major, uint8 _minor, uint8 _patch mapping(string => uint24) compatibleSTVersionRange; - event ChangeFactorySetupFee(uint256 _oldSetupCost, uint256 _newSetupCost, address _moduleFactory); - event ChangeFactoryUsageFee(uint256 _oldUsageCost, uint256 _newUsageCost, address _moduleFactory); - event ChangeFactorySubscriptionFee(uint256 _oldSubscriptionCost, uint256 _newMonthlySubscriptionCost, address _moduleFactory); - event GenerateModuleFromFactory( - address _module, - bytes32 indexed _moduleName, - address indexed _moduleFactory, - address _creator, - uint256 _timestamp - ); - event ChangeSTVersionBound(string _boundType, uint8 _major, uint8 _minor, uint8 _patch); - /** * @notice Constructor * @param _polyAddress Address of the polytoken diff --git a/contracts/modules/ModuleStorage.sol b/contracts/modules/ModuleStorage.sol new file mode 100644 index 000000000..cd52c9de2 --- /dev/null +++ b/contracts/modules/ModuleStorage.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.24; + +import "../interfaces/IERC20.sol"; + +/** + * @title Storage for Module contract + * @notice Contract is abstract + */ +contract ModuleStorage { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + */ + constructor (address _securityToken, address _polyAddress) public { + securityToken = _securityToken; + factory = msg.sender; + polyToken = IERC20(_polyAddress); + } + + address public factory; + + address public securityToken; + + bytes32 public constant FEE_ADMIN = "FEE_ADMIN"; + + IERC20 public polyToken; + +} diff --git a/contracts/modules/STO/CappedSTO.sol b/contracts/modules/STO/CappedSTO.sol index 60373ae37..3245aa3f5 100644 --- a/contracts/modules/STO/CappedSTO.sol +++ b/contracts/modules/STO/CappedSTO.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; @@ -8,14 +8,16 @@ import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title STO module for standard capped crowdsale */ -contract CappedSTO is ISTO, ReentrancyGuard { +contract CappedSTO is STO, ReentrancyGuard { using SafeMath for uint256; // Determine whether users can invest on behalf of a beneficiary bool public allowBeneficialInvestments = false; - // How many token units a buyer gets per wei / base unit of POLY + // How many token units a buyer gets (multiplied by 10^18) per wei / base unit of POLY + // If rate is 10^18, buyer will get 1 token unit for every wei / base unit of poly. uint256 public rate; - //How many tokens this STO will be allowed to sell to investors + //How many token base units this STO will be allowed to sell to investors + // 1 full token = 10^decimals_of_token base units uint256 public cap; mapping (address => uint256) public investors; @@ -48,8 +50,8 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @notice Function used to intialize the contract variables * @param _startTime Unix timestamp at which offering get started * @param _endTime Unix timestamp at which offering get ended - * @param _cap Maximum No. of tokens for sale - * @param _rate Token units a buyer gets per wei / base unit of POLY + * @param _cap Maximum No. of token base units for sale + * @param _rate Token units a buyer gets multiplied by 10^18 per wei / base unit of POLY * @param _fundRaiseTypes Type of currency used to collect the funds * @param _fundsReceiver Ethereum account address to hold the funds */ @@ -64,6 +66,7 @@ contract CappedSTO is ISTO, ReentrancyGuard { public onlyFactory { + require(endTime == 0, "Already configured"); require(_rate > 0, "Rate of token should be greater than 0"); require(_fundsReceiver != address(0), "Zero address is not permitted"); /*solium-disable-next-line security/no-block-members*/ @@ -108,10 +111,10 @@ contract CappedSTO is ISTO, ReentrancyGuard { require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "Mode of investment is not ETH"); uint256 weiAmount = msg.value; - _processTx(_beneficiary, weiAmount); + uint256 refund = _processTx(_beneficiary, weiAmount); + weiAmount = weiAmount.sub(refund); - _forwardFunds(); - _postValidatePurchase(_beneficiary, weiAmount); + _forwardFunds(refund); } /** @@ -121,9 +124,8 @@ contract CappedSTO is ISTO, ReentrancyGuard { function buyTokensWithPoly(uint256 _investedPOLY) public nonReentrant{ require(!paused, "Should not be paused"); require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Mode of investment is not POLY"); - _processTx(msg.sender, _investedPOLY); - _forwardPoly(msg.sender, wallet, _investedPOLY); - _postValidatePurchase(msg.sender, _investedPOLY); + uint256 refund = _processTx(msg.sender, _investedPOLY); + _forwardPoly(msg.sender, wallet, _investedPOLY.sub(refund)); } /** @@ -153,10 +155,11 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @notice Return the STO details * @return Unixtimestamp at which offering gets start. * @return Unixtimestamp at which offering ends. - * @return Number of tokens this STO will be allowed to sell to investors. + * @return Number of token base units this STO will be allowed to sell to investors. + * @return Token units a buyer gets(multiplied by 10^18) per wei / base unit of POLY * @return Amount of funds raised * @return Number of individual investors this STO have. - * @return Amount of tokens get sold. + * @return Amount of tokens get sold. * @return Boolean value to justify whether the fund raise type is POLY or not, i.e true for POLY. */ function getSTODetails() public view returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, bool) { @@ -180,11 +183,13 @@ contract CappedSTO is ISTO, ReentrancyGuard { * @param _beneficiary Address performing the token purchase * @param _investedAmount Value in wei involved in the purchase */ - function _processTx(address _beneficiary, uint256 _investedAmount) internal { + function _processTx(address _beneficiary, uint256 _investedAmount) internal returns(uint256 refund) { _preValidatePurchase(_beneficiary, _investedAmount); // calculate token amount to be created - uint256 tokens = _getTokenAmount(_investedAmount); + uint256 tokens; + (tokens, refund) = _getTokenAmount(_investedAmount); + _investedAmount = _investedAmount.sub(refund); // update state if (fundRaiseTypes[uint8(FundRaiseType.POLY)]) { @@ -196,8 +201,6 @@ contract CappedSTO is ISTO, ReentrancyGuard { _processPurchase(_beneficiary, tokens); emit TokenPurchase(msg.sender, _beneficiary, _investedAmount, tokens); - - _updatePurchasingState(_beneficiary, _investedAmount); } /** @@ -209,19 +212,10 @@ contract CappedSTO is ISTO, ReentrancyGuard { function _preValidatePurchase(address _beneficiary, uint256 _investedAmount) internal view { require(_beneficiary != address(0), "Beneficiary address should not be 0x"); require(_investedAmount != 0, "Amount invested should not be equal to 0"); - require(totalTokensSold.add(_getTokenAmount(_investedAmount)) <= cap, "Investment more than cap is not allowed"); /*solium-disable-next-line security/no-block-members*/ require(now >= startTime && now <= endTime, "Offering is closed/Not yet started"); } - /** - * @notice Validation of an executed purchase. - Observe state and use revert statements to undo rollback when valid conditions are not met. - */ - function _postValidatePurchase(address /*_beneficiary*/, uint256 /*_investedAmount*/) internal pure { - // optional override - } - /** * @notice Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends its tokens. @@ -246,28 +240,31 @@ contract CappedSTO is ISTO, ReentrancyGuard { _deliverTokens(_beneficiary, _tokenAmount); } - /** - * @notice Overrides for extensions that require an internal state to check for validity - (current user contributions, etc.) - */ - function _updatePurchasingState(address /*_beneficiary*/, uint256 /*_investedAmount*/) internal pure { - // optional override - } - /** * @notice Overrides to extend the way in which ether is converted to tokens. * @param _investedAmount Value in wei to be converted into tokens * @return Number of tokens that can be purchased with the specified _investedAmount + * @return Remaining amount that should be refunded to the investor */ - function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256) { - return _investedAmount.mul(rate); + function _getTokenAmount(uint256 _investedAmount) internal view returns (uint256 tokens, uint256 refund) { + tokens = _investedAmount.mul(rate); + tokens = tokens.div(uint256(10) ** 18); + if (totalTokensSold.add(tokens) > cap) { + tokens = cap.sub(totalTokensSold); + } + uint256 granularity = ISecurityToken(securityToken).granularity(); + tokens = tokens.div(granularity); + tokens = tokens.mul(granularity); + require(tokens > 0, "Cap reached"); + refund = _investedAmount.sub((tokens.mul(uint256(10) ** 18)).div(rate)); } /** * @notice Determines how ETH is stored/forwarded on purchases. */ - function _forwardFunds() internal { - wallet.transfer(msg.value); + function _forwardFunds(uint256 _refund) internal { + wallet.transfer(msg.value.sub(_refund)); + msg.sender.transfer(_refund); } /** diff --git a/contracts/modules/STO/CappedSTOFactory.sol b/contracts/modules/STO/CappedSTOFactory.sol index 89f3311d2..273389737 100644 --- a/contracts/modules/STO/CappedSTOFactory.sol +++ b/contracts/modules/STO/CappedSTOFactory.sol @@ -16,10 +16,10 @@ contract CappedSTOFactory is ModuleFactory { constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - version = "1.0.0"; + version = "2.1.0"; name = "CappedSTO"; title = "Capped STO"; - description = "Use to collects the funds and once the cap is reached then investment will be no longer entertained"; + description = "This smart contract creates a maximum number of tokens (i.e. hard cap) which the total aggregate of tokens acquired by all investors cannot exceed. Security tokens are sent to the investor upon reception of the funds (ETH or POLY), and any security tokens left upon termination of the offering will not be minted."; compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); } diff --git a/contracts/modules/STO/PreSaleSTO.sol b/contracts/modules/STO/PreSaleSTO.sol index 7378fb06b..4dacaa2d7 100644 --- a/contracts/modules/STO/PreSaleSTO.sol +++ b/contracts/modules/STO/PreSaleSTO.sol @@ -1,13 +1,13 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title STO module for private presales */ -contract PreSaleSTO is ISTO { +contract PreSaleSTO is STO { using SafeMath for uint256; bytes32 public constant PRE_SALE_ADMIN = "PRE_SALE_ADMIN"; diff --git a/contracts/modules/STO/ProxyFactory/USDTieredSTOProxyFactory.sol b/contracts/modules/STO/ProxyFactory/USDTieredSTOProxyFactory.sol deleted file mode 100644 index d9a48b87d..000000000 --- a/contracts/modules/STO/ProxyFactory/USDTieredSTOProxyFactory.sol +++ /dev/null @@ -1,33 +0,0 @@ -pragma solidity ^0.4.24; - -import "../USDTieredSTO.sol"; -import "../../../interfaces/IUSDTieredSTOProxy.sol"; - -contract USDTieredSTOProxyFactory is IUSDTieredSTOProxy { - - constructor() public { - - } - - /** - * @notice Deploys the STO. - * @param _securityToken Contract address of the securityToken - * @param _polyAddress Contract address of the PolyToken. - * @param _factoryAddress Contract address of the factory - * @return address Address of the deployed STO - */ - function deploySTO(address _securityToken, address _polyAddress, address _factoryAddress) external returns (address) { - address newSecurityTokenAddress = new USDTieredSTO(_securityToken, _polyAddress, _factoryAddress); - return newSecurityTokenAddress; - } - - /** - * @notice Used to get the init function signature - * @param _contractAddress Address of the STO contract - * @return bytes4 - */ - function getInitFunction(address _contractAddress) external returns (bytes4) { - return USDTieredSTO(_contractAddress).getInitFunction(); - } - -} diff --git a/contracts/modules/STO/ISTO.sol b/contracts/modules/STO/STO.sol similarity index 70% rename from contracts/modules/STO/ISTO.sol rename to contracts/modules/STO/STO.sol index 6ed1d5ba1..d05de918e 100644 --- a/contracts/modules/STO/ISTO.sol +++ b/contracts/modules/STO/STO.sol @@ -3,46 +3,21 @@ pragma solidity ^0.4.24; import "../../Pausable.sol"; import "../Module.sol"; import "../../interfaces/IERC20.sol"; +import "../../interfaces/ISTO.sol"; +import "./STOStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title Interface to be implemented by all STO modules */ -contract ISTO is Module, Pausable { +contract STO is ISTO, STOStorage, Module, Pausable { using SafeMath for uint256; - enum FundRaiseType { ETH, POLY, DAI } - mapping (uint8 => bool) public fundRaiseTypes; - mapping (uint8 => uint256) public fundsRaised; - - // Start time of the STO - uint256 public startTime; - // End time of the STO - uint256 public endTime; - // Time STO was paused - uint256 public pausedTime; - // Number of individual investors - uint256 public investorCount; - // Address where ETH & POLY funds are delivered - address public wallet; - // Final amount of tokens sold - uint256 public totalTokensSold; + enum FundRaiseType { ETH, POLY, SC } // Event event SetFundRaiseTypes(FundRaiseType[] _fundRaiseTypes); - /** - * @notice Reclaims ERC20Basic compatible tokens - * @dev We duplicate here due to the overriden owner & onlyOwner - * @param _tokenContract The address of the token contract - */ - function reclaimERC20(address _tokenContract) external onlyOwner { - require(_tokenContract != address(0), "Invalid address"); - IERC20 token = IERC20(_tokenContract); - uint256 balance = token.balanceOf(address(this)); - require(token.transfer(msg.sender, balance), "Transfer failed"); - } - /** * @notice Returns funds raised by the STO */ @@ -50,11 +25,6 @@ contract ISTO is Module, Pausable { return fundsRaised[uint8(_fundRaiseType)]; } - /** - * @notice Returns the total no. of tokens sold - */ - function getTokensSold() public view returns (uint256); - /** * @notice Pause (overridden function) */ @@ -73,14 +43,34 @@ contract ISTO is Module, Pausable { function _setFundRaiseType(FundRaiseType[] _fundRaiseTypes) internal { // FundRaiseType[] parameter type ensures only valid values for _fundRaiseTypes - require(_fundRaiseTypes.length > 0, "Raise type is not specified"); + require(_fundRaiseTypes.length > 0 && _fundRaiseTypes.length <= 3, "Raise type is not specified"); fundRaiseTypes[uint8(FundRaiseType.ETH)] = false; fundRaiseTypes[uint8(FundRaiseType.POLY)] = false; - fundRaiseTypes[uint8(FundRaiseType.DAI)] = false; + fundRaiseTypes[uint8(FundRaiseType.SC)] = false; for (uint8 j = 0; j < _fundRaiseTypes.length; j++) { fundRaiseTypes[uint8(_fundRaiseTypes[j])] = true; } emit SetFundRaiseTypes(_fundRaiseTypes); } + /** + * @notice Reclaims ERC20Basic compatible tokens + * @dev We duplicate here due to the overriden owner & onlyOwner + * @param _tokenContract The address of the token contract + */ + function reclaimERC20(address _tokenContract) external onlyOwner { + require(_tokenContract != address(0), "Invalid address"); + IERC20 token = IERC20(_tokenContract); + uint256 balance = token.balanceOf(address(this)); + require(token.transfer(msg.sender, balance), "Transfer failed"); + } + + /** + * @notice Reclaims ETH + * @dev We duplicate here due to the overriden owner & onlyOwner + */ + function reclaimETH() external onlyOwner { + msg.sender.transfer(address(this).balance); + } + } diff --git a/contracts/modules/STO/STOStorage.sol b/contracts/modules/STO/STOStorage.sol new file mode 100644 index 000000000..2e2401fb0 --- /dev/null +++ b/contracts/modules/STO/STOStorage.sol @@ -0,0 +1,24 @@ +pragma solidity ^0.4.24; + +/** + * @title Storage layout for the STO contract + */ +contract STOStorage { + + mapping (uint8 => bool) public fundRaiseTypes; + mapping (uint8 => uint256) public fundsRaised; + + // Start time of the STO + uint256 public startTime; + // End time of the STO + uint256 public endTime; + // Time STO was paused + uint256 public pausedTime; + // Number of individual investors + uint256 public investorCount; + // Address where ETH & POLY funds are delivered + address public wallet; + // Final amount of tokens sold + uint256 public totalTokensSold; + +} diff --git a/contracts/modules/STO/USDTieredSTO.sol b/contracts/modules/STO/USDTieredSTO.sol index 894744451..e40e4c6b0 100644 --- a/contracts/modules/STO/USDTieredSTO.sol +++ b/contracts/modules/STO/USDTieredSTO.sol @@ -1,88 +1,22 @@ pragma solidity ^0.4.24; -import "./ISTO.sol"; +import "./STO.sol"; import "../../interfaces/ISecurityToken.sol"; import "../../interfaces/IOracle.sol"; import "../../RegistryUpdater.sol"; import "../../libraries/DecimalMath.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; +import "../../storage/USDTieredSTOStorage.sol"; /** * @title STO module for standard capped crowdsale */ -contract USDTieredSTO is ISTO, ReentrancyGuard { +contract USDTieredSTO is USDTieredSTOStorage, STO, ReentrancyGuard { using SafeMath for uint256; - ///////////// - // Storage // - ///////////// - - string public POLY_ORACLE = "PolyUsdOracle"; - string public ETH_ORACLE = "EthUsdOracle"; - mapping (bytes32 => mapping (bytes32 => string)) oracleKeys; - - IERC20 public usdToken; - - // Determine whether users can invest on behalf of a beneficiary - bool public allowBeneficialInvestments = false; - - // Address where ETH, POLY & DAI funds are delivered - address public wallet; - - // Address of issuer reserve wallet for unsold tokens - address public reserveWallet; - - // How many token units a buyer gets per USD per tier (multiplied by 10**18) - uint256[] public ratePerTier; - - // How many token units a buyer gets per USD per tier (multiplied by 10**18) when investing in POLY up to tokensPerTierDiscountPoly - uint256[] public ratePerTierDiscountPoly; - - // How many tokens are available in each tier (relative to totalSupply) - uint256[] public tokensPerTierTotal; - - // How many token units are available in each tier (relative to totalSupply) at the ratePerTierDiscountPoly rate - uint256[] public tokensPerTierDiscountPoly; - - // How many tokens have been minted in each tier (relative to totalSupply) - uint256[] public mintedPerTierTotal; - - // How many tokens have been minted in each tier (relative to totalSupply) for each fund raise type - mapping (uint8 => uint256[]) public mintedPerTier; - - // How many tokens have been minted in each tier (relative to totalSupply) at discounted POLY rate - uint256[] public mintedPerTierDiscountPoly; - - // Current tier - uint8 public currentTier; - - // Amount of USD funds raised - uint256 public fundsRaisedUSD; - - // Amount in USD invested by each address - mapping (address => uint256) public investorInvestedUSD; - - // Amount in fund raise type invested by each investor - mapping (address => mapping (uint8 => uint256)) public investorInvested; - - // List of accredited investors - mapping (address => bool) public accredited; - - // Default limit in USD for non-accredited investors multiplied by 10**18 - uint256 public nonAccreditedLimitUSD; - - // Overrides for default limit in USD for non-accredited investors multiplied by 10**18 - mapping (address => uint256) public nonAccreditedLimitUSDOverride; - - // Minimum investable amount in USD - uint256 public minimumInvestmentUSD; - - // Whether or not the STO has been finalized - bool public isFinalized; - - // Final amount of tokens returned to issuer - uint256 public finalAmountReturned; + string public constant POLY_ORACLE = "PolyUsdOracle"; + string public constant ETH_ORACLE = "EthUsdOracle"; //////////// // Events // @@ -97,7 +31,7 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { uint256 _tokens, uint256 _usdAmount, uint256 _tierPrice, - uint8 _tier + uint256 _tier ); event FundsReceived( address indexed _purchaser, @@ -108,20 +42,11 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { uint256 _spentValue, uint256 _rate ); - event FundsReceivedPOLY( - address indexed _purchaser, - address indexed _beneficiary, - uint256 _usdAmount, - uint256 _receivedValue, - uint256 _spentValue, - uint256 _rate - ); - event ReserveTokenMint(address indexed _owner, address indexed _wallet, uint256 _tokens, uint8 _latestTier); - + event ReserveTokenMint(address indexed _owner, address indexed _wallet, uint256 _tokens, uint256 _latestTier); event SetAddresses( address indexed _wallet, address indexed _reserveWallet, - address indexed _usdToken + address[] _usdTokens ); event SetLimits( uint256 _nonAccreditedLimitUSD, @@ -143,19 +68,19 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { /////////////// modifier validETH { - require(_getOracle(bytes32("ETH"), bytes32("USD")) != address(0), "Invalid ETHUSD Oracle"); - require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "Fund raise in ETH should be allowed"); + require(_getOracle(bytes32("ETH"), bytes32("USD")) != address(0), "Invalid Oracle"); + require(fundRaiseTypes[uint8(FundRaiseType.ETH)], "ETH not allowed"); _; } modifier validPOLY { - require(_getOracle(bytes32("POLY"), bytes32("USD")) != address(0), "Invalid POLYUSD Oracle"); - require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "Fund raise in POLY should be allowed"); + require(_getOracle(bytes32("POLY"), bytes32("USD")) != address(0), "Invalid Oracle"); + require(fundRaiseTypes[uint8(FundRaiseType.POLY)], "POLY not allowed"); _; } - modifier validDAI { - require(fundRaiseTypes[uint8(FundRaiseType.DAI)], "Fund raise in DAI should be allowed"); + modifier validSC(address _usdToken) { + require(fundRaiseTypes[uint8(FundRaiseType.SC)] && usdTokenEnabled[_usdToken], "USD not allowed"); _; } @@ -163,11 +88,10 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { // STO Configuration // /////////////////////// - constructor (address _securityToken, address _polyAddress, address _factory) public Module(_securityToken, _polyAddress) { - oracleKeys[bytes32("ETH")][bytes32("USD")] = ETH_ORACLE; - oracleKeys[bytes32("POLY")][bytes32("USD")] = POLY_ORACLE; - require(_factory != address(0), "In-valid address"); - factory = _factory; + constructor (address _securityToken, address _polyAddress) + public + Module(_securityToken, _polyAddress) + { } /** @@ -181,7 +105,7 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @param _fundRaiseTypes Types of currency used to collect the funds * @param _wallet Ethereum account address to hold the funds * @param _reserveWallet Ethereum account address to receive unsold tokens - * @param _usdToken Contract address of the stable coin + * @param _usdTokens Array of contract addressess of the stable coins */ function configure( uint256 _startTime, @@ -195,71 +119,125 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { FundRaiseType[] _fundRaiseTypes, address _wallet, address _reserveWallet, - address _usdToken + address[] _usdTokens ) public onlyFactory { - modifyTimes(_startTime, _endTime); - // NB - modifyTiers must come before modifyFunding - modifyTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); - // NB - modifyFunding must come before modifyAddresses - modifyFunding(_fundRaiseTypes); - modifyAddresses(_wallet, _reserveWallet, _usdToken); - modifyLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); + oracleKeys[bytes32("ETH")][bytes32("USD")] = ETH_ORACLE; + oracleKeys[bytes32("POLY")][bytes32("USD")] = POLY_ORACLE; + require(endTime == 0, "Already configured"); + _modifyTimes(_startTime, _endTime); + _modifyTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); + // NB - _setFundRaiseType must come before modifyAddresses + _setFundRaiseType(_fundRaiseTypes); + _modifyAddresses(_wallet, _reserveWallet, _usdTokens); + _modifyLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); } - function modifyFunding(FundRaiseType[] _fundRaiseTypes) public onlyFactoryOrOwner { + /** + * @dev Modifies fund raise types + * @param _fundRaiseTypes Array of fund raise types to allow + */ + function modifyFunding(FundRaiseType[] _fundRaiseTypes) external onlyOwner { /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO shouldn't be started"); + require(now < startTime, "STO already started"); _setFundRaiseType(_fundRaiseTypes); - uint256 length = getNumberOfTiers(); - mintedPerTierTotal = new uint256[](length); - mintedPerTierDiscountPoly = new uint256[](length); - for (uint8 i = 0; i < _fundRaiseTypes.length; i++) { - mintedPerTier[uint8(_fundRaiseTypes[i])] = new uint256[](length); - } } + /** + * @dev modifies max non accredited invets limit and overall minimum investment limit + * @param _nonAccreditedLimitUSD max non accredited invets limit + * @param _minimumInvestmentUSD overall minimum investment limit + */ function modifyLimits( uint256 _nonAccreditedLimitUSD, uint256 _minimumInvestmentUSD - ) public onlyFactoryOrOwner { + ) external onlyOwner { /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO shouldn't be started"); + require(now < startTime, "STO already started"); + _modifyLimits(_nonAccreditedLimitUSD, _minimumInvestmentUSD); + } + + /** + * @dev modifiers STO tiers. All tiers must be passed, can not edit specific tiers. + * @param _ratePerTier Array of rates per tier + * @param _ratePerTierDiscountPoly Array of discounted poly rates per tier + * @param _tokensPerTierTotal Array of total tokens per tier + * @param _tokensPerTierDiscountPoly Array of discounted tokens per tier + */ + function modifyTiers( + uint256[] _ratePerTier, + uint256[] _ratePerTierDiscountPoly, + uint256[] _tokensPerTierTotal, + uint256[] _tokensPerTierDiscountPoly + ) external onlyOwner { + /*solium-disable-next-line security/no-block-members*/ + require(now < startTime, "STO already started"); + _modifyTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); + } + + /** + * @dev Modifies STO start and end times + * @param _startTime start time of sto + * @param _endTime end time of sto + */ + function modifyTimes( + uint256 _startTime, + uint256 _endTime + ) external onlyOwner { + /*solium-disable-next-line security/no-block-members*/ + require(now < startTime, "STO already started"); + _modifyTimes(_startTime, _endTime); + } + + /** + * @dev Modifies addresses used as wallet, reserve wallet and usd token + * @param _wallet Address of wallet where funds are sent + * @param _reserveWallet Address of wallet where unsold tokens are sent + * @param _usdTokens Address of usd tokens + */ + function modifyAddresses( + address _wallet, + address _reserveWallet, + address[] _usdTokens + ) external onlyOwner { + _modifyAddresses(_wallet, _reserveWallet, _usdTokens); + } + + function _modifyLimits( + uint256 _nonAccreditedLimitUSD, + uint256 _minimumInvestmentUSD + ) internal { minimumInvestmentUSD = _minimumInvestmentUSD; nonAccreditedLimitUSD = _nonAccreditedLimitUSD; emit SetLimits(minimumInvestmentUSD, nonAccreditedLimitUSD); } - function modifyTiers( + function _modifyTiers( uint256[] _ratePerTier, uint256[] _ratePerTierDiscountPoly, uint256[] _tokensPerTierTotal, uint256[] _tokensPerTierDiscountPoly - ) public onlyFactoryOrOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO shouldn't be started"); - require(_tokensPerTierTotal.length > 0, "Length should be > 0"); - require(_ratePerTier.length == _tokensPerTierTotal.length, "Mismatch b/w rates & tokens / tier"); - require(_ratePerTierDiscountPoly.length == _tokensPerTierTotal.length, "Mismatch b/w discount rates & tokens / tier"); - require(_tokensPerTierDiscountPoly.length == _tokensPerTierTotal.length, "Mismatch b/w discount tokens / tier & tokens / tier"); - for (uint8 i = 0; i < _ratePerTier.length; i++) { - require(_ratePerTier[i] > 0, "Rate > 0"); - require(_tokensPerTierTotal[i] > 0, "Tokens per tier > 0"); - require(_tokensPerTierDiscountPoly[i] <= _tokensPerTierTotal[i], "Discounted tokens / tier <= tokens / tier"); - require(_ratePerTierDiscountPoly[i] <= _ratePerTier[i], "Discounted rate / tier <= rate / tier"); + ) internal { + require(_tokensPerTierTotal.length > 0, "No tiers provided"); + require(_ratePerTier.length == _tokensPerTierTotal.length && + _ratePerTierDiscountPoly.length == _tokensPerTierTotal.length && + _tokensPerTierDiscountPoly.length == _tokensPerTierTotal.length, + "Tier data length mismatch" + ); + delete tiers; + for (uint256 i = 0; i < _ratePerTier.length; i++) { + require(_ratePerTier[i] > 0, "Invalid rate"); + require(_tokensPerTierTotal[i] > 0, "Invalid token amount"); + require(_tokensPerTierDiscountPoly[i] <= _tokensPerTierTotal[i], "Too many discounted tokens"); + require(_ratePerTierDiscountPoly[i] <= _ratePerTier[i], "Invalid discount"); + tiers.push(Tier(_ratePerTier[i], _ratePerTierDiscountPoly[i], _tokensPerTierTotal[i], _tokensPerTierDiscountPoly[i], 0, 0)); } - ratePerTier = _ratePerTier; - ratePerTierDiscountPoly = _ratePerTierDiscountPoly; - tokensPerTierTotal = _tokensPerTierTotal; - tokensPerTierDiscountPoly = _tokensPerTierDiscountPoly; emit SetTiers(_ratePerTier, _ratePerTierDiscountPoly, _tokensPerTierTotal, _tokensPerTierDiscountPoly); } - function modifyTimes( + function _modifyTimes( uint256 _startTime, uint256 _endTime - ) public onlyFactoryOrOwner { - /*solium-disable-next-line security/no-block-members*/ - require((startTime == 0) || (now < startTime), "Invalid startTime"); + ) internal { /*solium-disable-next-line security/no-block-members*/ require((_endTime > _startTime) && (_startTime > now), "Invalid times"); startTime = _startTime; @@ -267,21 +245,27 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { emit SetTimes(_startTime, _endTime); } - function modifyAddresses( + function _modifyAddresses( address _wallet, address _reserveWallet, - address _usdToken - ) public onlyFactoryOrOwner { - /*solium-disable-next-line security/no-block-members*/ - require(now < startTime, "STO shouldn't be started"); - require(_wallet != address(0) && _reserveWallet != address(0), "Invalid address"); - if (fundRaiseTypes[uint8(FundRaiseType.DAI)]) { - require(_usdToken != address(0), "Invalid address"); - } + address[] _usdTokens + ) internal { + require(_wallet != address(0) && _reserveWallet != address(0), "Invalid wallet"); wallet = _wallet; reserveWallet = _reserveWallet; - usdToken = IERC20(_usdToken); - emit SetAddresses(_wallet, _reserveWallet, _usdToken); + _modifyUSDTokens(_usdTokens); + } + + function _modifyUSDTokens(address[] _usdTokens) internal { + for(uint256 i = 0; i < usdTokens.length; i++) { + usdTokenEnabled[usdTokens[i]] = false; + } + usdTokens = _usdTokens; + for(i = 0; i < _usdTokens.length; i++) { + require(_usdTokens[i] != address(0) && _usdTokens[i] != address(polyToken), "Invalid USD token"); + usdTokenEnabled[_usdTokens[i]] = true; + } + emit SetAddresses(wallet, reserveWallet, _usdTokens); } //////////////////// @@ -292,20 +276,23 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @notice Finalizes the STO and mint remaining tokens to reserve address * @notice Reserve address must be whitelisted to successfully finalize */ - function finalize() public onlyOwner { - require(!isFinalized, "STO is already finalized"); + function finalize() external onlyOwner { + require(!isFinalized, "STO is finalized"); isFinalized = true; uint256 tempReturned; uint256 tempSold; uint256 remainingTokens; - for (uint8 i = 0; i < tokensPerTierTotal.length; i++) { - remainingTokens = tokensPerTierTotal[i].sub(mintedPerTierTotal[i]); + for (uint256 i = 0; i < tiers.length; i++) { + remainingTokens = tiers[i].tokenTotal.sub(tiers[i].mintedTotal); tempReturned = tempReturned.add(remainingTokens); - tempSold = tempSold.add(mintedPerTierTotal[i]); + tempSold = tempSold.add(tiers[i].mintedTotal); if (remainingTokens > 0) { - mintedPerTierTotal[i] = tokensPerTierTotal[i]; + tiers[i].mintedTotal = tiers[i].tokenTotal; } } + uint256 granularity = ISecurityToken(securityToken).granularity(); + tempReturned = tempReturned.div(granularity); + tempReturned = tempReturned.mul(granularity); require(ISecurityToken(securityToken).mint(reserveWallet, tempReturned), "Error in minting"); emit ReserveTokenMint(msg.sender, reserveWallet, tempReturned, currentTier); finalAmountReturned = tempReturned; @@ -317,10 +304,15 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @param _investors Array of investor addresses to modify * @param _accredited Array of bools specifying accreditation status */ - function changeAccredited(address[] _investors, bool[] _accredited) public onlyOwner { - require(_investors.length == _accredited.length, "Array length mismatch"); + function changeAccredited(address[] _investors, bool[] _accredited) external onlyOwner { + require(_investors.length == _accredited.length, "Array mismatch"); for (uint256 i = 0; i < _investors.length; i++) { - accredited[_investors[i]] = _accredited[i]; + if (_accredited[i]) { + investors[_investors[i]].accredited = uint8(1); + } else { + investors[_investors[i]].accredited = uint8(0); + } + _addToInvestorsList(_investors[i]); emit SetAccredited(_investors[i], _accredited[i]); } } @@ -330,22 +322,46 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @param _investors Array of investor addresses to modify * @param _nonAccreditedLimit Array of uints specifying non-accredited limits */ - function changeNonAccreditedLimit(address[] _investors, uint256[] _nonAccreditedLimit) public onlyOwner { + function changeNonAccreditedLimit(address[] _investors, uint256[] _nonAccreditedLimit) external onlyOwner { //nonAccreditedLimitUSDOverride - require(_investors.length == _nonAccreditedLimit.length, "Array length mismatch"); + require(_investors.length == _nonAccreditedLimit.length, "Array mismatch"); for (uint256 i = 0; i < _investors.length; i++) { - require(_nonAccreditedLimit[i] > 0, "Limit can not be 0"); - nonAccreditedLimitUSDOverride[_investors[i]] = _nonAccreditedLimit[i]; + investors[_investors[i]].nonAccreditedLimitUSDOverride = _nonAccreditedLimit[i]; + _addToInvestorsList(_investors[i]); emit SetNonAccreditedLimit(_investors[i], _nonAccreditedLimit[i]); } } + function _addToInvestorsList(address _investor) internal { + if (investors[_investor].seen == uint8(0)) { + investors[_investor].seen = uint8(1); + investorsList.push(_investor); + } + } + + /** + * @notice Returns investor accredited & non-accredited override informatiomn + * @return address[] list of all configured investors + * @return bool[] whether investor is accredited + * @return uint256[] any USD overrides for non-accredited limits for the investor + */ + function getAccreditedData() external view returns (address[], bool[], uint256[]) { + bool[] memory accrediteds = new bool[](investorsList.length); + uint256[] memory nonAccreditedLimitUSDOverrides = new uint256[](investorsList.length); + uint256 i; + for (i = 0; i < investorsList.length; i++) { + accrediteds[i] = (investors[investorsList[i]].accredited == uint8(0)? false: true); + nonAccreditedLimitUSDOverrides[i] = investors[investorsList[i]].nonAccreditedLimitUSDOverride; + } + return (investorsList, accrediteds, nonAccreditedLimitUSDOverrides); + } + /** * @notice Function to set allowBeneficialInvestments (allow beneficiary to be different to funder) * @param _allowBeneficialInvestments Boolean to allow or disallow beneficial investments */ - function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) public onlyOwner { - require(_allowBeneficialInvestments != allowBeneficialInvestments, "Value unchanged"); + function changeAllowBeneficialInvestments(bool _allowBeneficialInvestments) external onlyOwner { + require(_allowBeneficialInvestments != allowBeneficialInvestments); allowBeneficialInvestments = _allowBeneficialInvestments; emit SetAllowBeneficialInvestments(allowBeneficialInvestments); } @@ -358,16 +374,32 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @notice fallback function - assumes ETH being invested */ function () external payable { - buyWithETH(msg.sender); + buyWithETHRateLimited(msg.sender, 0); + } + + // Buy functions without rate restriction + function buyWithETH(address _beneficiary) external payable { + buyWithETHRateLimited(_beneficiary, 0); + } + + function buyWithPOLY(address _beneficiary, uint256 _investedPOLY) external { + buyWithPOLYRateLimited(_beneficiary, _investedPOLY, 0); + } + + function buyWithUSD(address _beneficiary, uint256 _investedSC, IERC20 _usdToken) external { + buyWithUSDRateLimited(_beneficiary, _investedSC, 0, _usdToken); } /** * @notice Purchase tokens using ETH * @param _beneficiary Address where security tokens will be sent + * @param _minTokens Minumum number of tokens to buy or else revert */ - function buyWithETH(address _beneficiary) public payable validETH { + function buyWithETHRateLimited(address _beneficiary, uint256 _minTokens) public payable validETH { uint256 rate = getRate(FundRaiseType.ETH); + uint256 initialMinted = getTokensMinted(); (uint256 spentUSD, uint256 spentValue) = _buyTokens(_beneficiary, msg.value, rate, FundRaiseType.ETH); + require(getTokensMinted().sub(initialMinted) >= _minTokens, "Insufficient minted"); // Modify storage investorInvested[_beneficiary][uint8(FundRaiseType.ETH)] = investorInvested[_beneficiary][uint8(FundRaiseType.ETH)].add(spentValue); fundsRaised[uint8(FundRaiseType.ETH)] = fundsRaised[uint8(FundRaiseType.ETH)].add(spentValue); @@ -382,38 +414,46 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @notice Purchase tokens using POLY * @param _beneficiary Address where security tokens will be sent * @param _investedPOLY Amount of POLY invested + * @param _minTokens Minumum number of tokens to buy or else revert */ - function buyWithPOLY(address _beneficiary, uint256 _investedPOLY) public validPOLY { - _buyWithTokens(_beneficiary, _investedPOLY, FundRaiseType.POLY); + function buyWithPOLYRateLimited(address _beneficiary, uint256 _investedPOLY, uint256 _minTokens) public validPOLY { + _buyWithTokens(_beneficiary, _investedPOLY, FundRaiseType.POLY, _minTokens, polyToken); } /** - * @notice Purchase tokens using POLY + * @notice Purchase tokens using Stable coins * @param _beneficiary Address where security tokens will be sent - * @param _investedDAI Amount of POLY invested + * @param _investedSC Amount of Stable coins invested + * @param _minTokens Minumum number of tokens to buy or else revert + * @param _usdToken Address of USD stable coin to buy tokens with */ - function buyWithUSD(address _beneficiary, uint256 _investedDAI) public validDAI { - _buyWithTokens(_beneficiary, _investedDAI, FundRaiseType.DAI); + function buyWithUSDRateLimited(address _beneficiary, uint256 _investedSC, uint256 _minTokens, IERC20 _usdToken) + public validSC(_usdToken) + { + _buyWithTokens(_beneficiary, _investedSC, FundRaiseType.SC, _minTokens, _usdToken); } - function _buyWithTokens(address _beneficiary, uint256 _tokenAmount, FundRaiseType _fundRaiseType) internal { - require(_fundRaiseType == FundRaiseType.POLY || _fundRaiseType == FundRaiseType.DAI, "POLY & DAI supported"); + function _buyWithTokens(address _beneficiary, uint256 _tokenAmount, FundRaiseType _fundRaiseType, uint256 _minTokens, IERC20 _token) internal { + require(_fundRaiseType == FundRaiseType.POLY || _fundRaiseType == FundRaiseType.SC, "Invalid raise"); + uint256 initialMinted = getTokensMinted(); uint256 rate = getRate(_fundRaiseType); (uint256 spentUSD, uint256 spentValue) = _buyTokens(_beneficiary, _tokenAmount, rate, _fundRaiseType); + require(getTokensMinted().sub(initialMinted) >= _minTokens, "Insufficient minted"); // Modify storage investorInvested[_beneficiary][uint8(_fundRaiseType)] = investorInvested[_beneficiary][uint8(_fundRaiseType)].add(spentValue); fundsRaised[uint8(_fundRaiseType)] = fundsRaised[uint8(_fundRaiseType)].add(spentValue); - // Forward DAI to issuer wallet - IERC20 token = _fundRaiseType == FundRaiseType.POLY ? polyToken : usdToken; - require(token.transferFrom(msg.sender, wallet, spentValue), "Transfer failed"); + if(address(_token) != address(polyToken)) + stableCoinsRaised[address(_token)] = stableCoinsRaised[address(_token)].add(spentValue); + // Forward coins to issuer wallet + require(_token.transferFrom(msg.sender, wallet, spentValue), "Transfer failed"); emit FundsReceived(msg.sender, _beneficiary, spentUSD, _fundRaiseType, _tokenAmount, spentValue, rate); } /** * @notice Low level token purchase * @param _beneficiary Address where security tokens will be sent - * @param _investmentValue Amount of POLY, ETH or DAI invested - * @param _fundRaiseType Fund raise type (POLY, ETH, DAI) + * @param _investmentValue Amount of POLY, ETH or Stable coins invested + * @param _fundRaiseType Fund raise type (POLY, ETH, SC) */ function _buyTokens( address _beneficiary, @@ -424,40 +464,29 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { internal nonReentrant whenNotPaused - returns(uint256, uint256) + returns(uint256 spentUSD, uint256 spentValue) { if (!allowBeneficialInvestments) { - require(_beneficiary == msg.sender, "Beneficiary does not match funder"); + require(_beneficiary == msg.sender, "Beneficiary != funder"); } - require(isOpen(), "STO is not open"); - require(_investmentValue > 0, "No funds were sent"); - - uint256 investedUSD = DecimalMath.mul(_rate, _investmentValue); - uint256 originalUSD = investedUSD; - - // Check for minimum investment - require(investedUSD.add(investorInvestedUSD[_beneficiary]) >= minimumInvestmentUSD, "Total investment < minimumInvestmentUSD"); + uint256 originalUSD = DecimalMath.mul(_rate, _investmentValue); + uint256 allowedUSD = _buyTokensChecks(_beneficiary, _investmentValue, originalUSD); - // Check for non-accredited cap - if (!accredited[_beneficiary]) { - uint256 investorLimitUSD = (nonAccreditedLimitUSDOverride[_beneficiary] == 0) ? nonAccreditedLimitUSD : nonAccreditedLimitUSDOverride[_beneficiary]; - require(investorInvestedUSD[_beneficiary] < investorLimitUSD, "Non-accredited investor has reached limit"); - if (investedUSD.add(investorInvestedUSD[_beneficiary]) > investorLimitUSD) - investedUSD = investorLimitUSD.sub(investorInvestedUSD[_beneficiary]); - } - uint256 spentUSD; - // Iterate over each tier and process payment - for (uint8 i = currentTier; i < ratePerTier.length; i++) { + for (uint256 i = currentTier; i < tiers.length; i++) { + bool gotoNextTier; + uint256 tempSpentUSD; // Update current tier if needed if (currentTier != i) currentTier = i; // If there are tokens remaining, process investment - if (mintedPerTierTotal[i] < tokensPerTierTotal[i]) - spentUSD = spentUSD.add(_calculateTier(_beneficiary, i, investedUSD.sub(spentUSD), _fundRaiseType)); - // If all funds have been spent, exit the loop - if (investedUSD == spentUSD) - break; + if (tiers[i].mintedTotal < tiers[i].tokenTotal) { + (tempSpentUSD, gotoNextTier) = _calculateTier(_beneficiary, i, allowedUSD.sub(spentUSD), _fundRaiseType); + spentUSD = spentUSD.add(tempSpentUSD); + // If all funds have been spent, exit the loop + if (!gotoNextTier) + break; + } } // Modify storage @@ -468,53 +497,143 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { fundsRaisedUSD = fundsRaisedUSD.add(spentUSD); } - // Calculate spent in base currency (ETH, DAI or POLY) - uint256 spentValue; - if (spentUSD == 0) { - spentValue = 0; - } else { - spentValue = DecimalMath.mul(DecimalMath.div(spentUSD, originalUSD), _investmentValue); + spentValue = DecimalMath.div(spentUSD, _rate); + } + + /** + * @notice Getter function for buyer to calculate how many tokens will they get + * @param _beneficiary Address where security tokens are to be sent + * @param _investmentValue Amount of POLY, ETH or Stable coins invested + * @param _fundRaiseType Fund raise type (POLY, ETH, SC) + */ + function buyTokensView( + address _beneficiary, + uint256 _investmentValue, + FundRaiseType _fundRaiseType + ) + external + view + returns(uint256 spentUSD, uint256 spentValue, uint256 tokensMinted) + { + require(_fundRaiseType == FundRaiseType.POLY || _fundRaiseType == FundRaiseType.SC || _fundRaiseType == FundRaiseType.ETH, "Invalid raise type"); + uint256 rate = getRate(_fundRaiseType); + uint256 originalUSD = DecimalMath.mul(rate, _investmentValue); + uint256 allowedUSD = _buyTokensChecks(_beneficiary, _investmentValue, originalUSD); + + // Iterate over each tier and process payment + for (uint256 i = currentTier; i < tiers.length; i++) { + bool gotoNextTier; + uint256 tempSpentUSD; + uint256 tempTokensMinted; + // If there are tokens remaining, process investment + if (tiers[i].mintedTotal < tiers[i].tokenTotal) { + (tempSpentUSD, gotoNextTier, tempTokensMinted) = _calculateTierView(i, allowedUSD.sub(spentUSD), _fundRaiseType); + spentUSD = spentUSD.add(tempSpentUSD); + tokensMinted = tokensMinted.add(tempTokensMinted); + // If all funds have been spent, exit the loop + if (!gotoNextTier) + break; + } } - // Return calculated amounts - return (spentUSD, spentValue); + spentValue = DecimalMath.div(spentUSD, rate); + } + + function _buyTokensChecks( + address _beneficiary, + uint256 _investmentValue, + uint256 investedUSD + ) + internal + view + returns(uint256 netInvestedUSD) + { + require(isOpen(), "STO not open"); + require(_investmentValue > 0, "No funds were sent"); + + // Check for minimum investment + require(investedUSD.add(investorInvestedUSD[_beneficiary]) >= minimumInvestmentUSD, "investment < minimumInvestmentUSD"); + netInvestedUSD = investedUSD; + // Check for non-accredited cap + if (investors[_beneficiary].accredited == uint8(0)) { + uint256 investorLimitUSD = (investors[_beneficiary].nonAccreditedLimitUSDOverride == 0) ? nonAccreditedLimitUSD : investors[_beneficiary].nonAccreditedLimitUSDOverride; + require(investorInvestedUSD[_beneficiary] < investorLimitUSD, "Over investor limit"); + if (investedUSD.add(investorInvestedUSD[_beneficiary]) > investorLimitUSD) + netInvestedUSD = investorLimitUSD.sub(investorInvestedUSD[_beneficiary]); + } } function _calculateTier( address _beneficiary, - uint8 _tier, + uint256 _tier, uint256 _investedUSD, FundRaiseType _fundRaiseType - ) + ) internal - returns(uint256) + returns(uint256 spentUSD, bool gotoNextTier) { // First purchase any discounted tokens if POLY investment - uint256 spentUSD; uint256 tierSpentUSD; uint256 tierPurchasedTokens; uint256 investedUSD = _investedUSD; + Tier storage tierData = tiers[_tier]; // Check whether there are any remaining discounted tokens - if ((_fundRaiseType == FundRaiseType.POLY) && (tokensPerTierDiscountPoly[_tier] > mintedPerTierDiscountPoly[_tier])) { - uint256 discountRemaining = tokensPerTierDiscountPoly[_tier].sub(mintedPerTierDiscountPoly[_tier]); - uint256 totalRemaining = tokensPerTierTotal[_tier].sub(mintedPerTierTotal[_tier]); + if ((_fundRaiseType == FundRaiseType.POLY) && (tierData.tokensDiscountPoly > tierData.mintedDiscountPoly)) { + uint256 discountRemaining = tierData.tokensDiscountPoly.sub(tierData.mintedDiscountPoly); + uint256 totalRemaining = tierData.tokenTotal.sub(tierData.mintedTotal); if (totalRemaining < discountRemaining) - (spentUSD, tierPurchasedTokens) = _purchaseTier(_beneficiary, ratePerTierDiscountPoly[_tier], totalRemaining, investedUSD, _tier); + (spentUSD, tierPurchasedTokens, gotoNextTier) = _purchaseTier(_beneficiary, tierData.rateDiscountPoly, totalRemaining, investedUSD, _tier); else - (spentUSD, tierPurchasedTokens) = _purchaseTier(_beneficiary, ratePerTierDiscountPoly[_tier], discountRemaining, investedUSD, _tier); + (spentUSD, tierPurchasedTokens, gotoNextTier) = _purchaseTier(_beneficiary, tierData.rateDiscountPoly, discountRemaining, investedUSD, _tier); investedUSD = investedUSD.sub(spentUSD); - mintedPerTierDiscountPoly[_tier] = mintedPerTierDiscountPoly[_tier].add(tierPurchasedTokens); - mintedPerTier[uint8(FundRaiseType.POLY)][_tier] = mintedPerTier[uint8(FundRaiseType.POLY)][_tier].add(tierPurchasedTokens); - mintedPerTierTotal[_tier] = mintedPerTierTotal[_tier].add(tierPurchasedTokens); + tierData.mintedDiscountPoly = tierData.mintedDiscountPoly.add(tierPurchasedTokens); + tierData.minted[uint8(_fundRaiseType)] = tierData.minted[uint8(_fundRaiseType)].add(tierPurchasedTokens); + tierData.mintedTotal = tierData.mintedTotal.add(tierPurchasedTokens); } // Now, if there is any remaining USD to be invested, purchase at non-discounted rate - if ((investedUSD > 0) && (tokensPerTierTotal[_tier].sub(mintedPerTierTotal[_tier]) > 0)) { - (tierSpentUSD, tierPurchasedTokens) = _purchaseTier(_beneficiary, ratePerTier[_tier], tokensPerTierTotal[_tier].sub(mintedPerTierTotal[_tier]), investedUSD, _tier); + if (investedUSD > 0 && + tierData.tokenTotal.sub(tierData.mintedTotal) > 0 && + (_fundRaiseType != FundRaiseType.POLY || tierData.tokensDiscountPoly <= tierData.mintedDiscountPoly) + ) { + (tierSpentUSD, tierPurchasedTokens, gotoNextTier) = _purchaseTier(_beneficiary, tierData.rate, tierData.tokenTotal.sub(tierData.mintedTotal), investedUSD, _tier); spentUSD = spentUSD.add(tierSpentUSD); - mintedPerTier[uint8(_fundRaiseType)][_tier] = mintedPerTier[uint8(_fundRaiseType)][_tier].add(tierPurchasedTokens); - mintedPerTierTotal[_tier] = mintedPerTierTotal[_tier].add(tierPurchasedTokens); + tierData.minted[uint8(_fundRaiseType)] = tierData.minted[uint8(_fundRaiseType)].add(tierPurchasedTokens); + tierData.mintedTotal = tierData.mintedTotal.add(tierPurchasedTokens); + } + } + + function _calculateTierView( + uint256 _tier, + uint256 _investedUSD, + FundRaiseType _fundRaiseType + ) + internal + view + returns(uint256 spentUSD, bool gotoNextTier, uint256 tokensMinted) + { + // First purchase any discounted tokens if POLY investment + uint256 tierSpentUSD; + uint256 tierPurchasedTokens; + Tier storage tierData = tiers[_tier]; + // Check whether there are any remaining discounted tokens + if ((_fundRaiseType == FundRaiseType.POLY) && (tierData.tokensDiscountPoly > tierData.mintedDiscountPoly)) { + uint256 discountRemaining = tierData.tokensDiscountPoly.sub(tierData.mintedDiscountPoly); + uint256 totalRemaining = tierData.tokenTotal.sub(tierData.mintedTotal); + if (totalRemaining < discountRemaining) + (spentUSD, tokensMinted, gotoNextTier) = _purchaseTierAmount(tierData.rateDiscountPoly, totalRemaining, _investedUSD); + else + (spentUSD, tokensMinted, gotoNextTier) = _purchaseTierAmount(tierData.rateDiscountPoly, discountRemaining, _investedUSD); + _investedUSD = _investedUSD.sub(spentUSD); + } + // Now, if there is any remaining USD to be invested, purchase at non-discounted rate + if (_investedUSD > 0 && + tierData.tokenTotal.sub(tierData.mintedTotal.add(tokensMinted)) > 0 && + (_fundRaiseType != FundRaiseType.POLY || tierData.tokensDiscountPoly <= tierData.mintedDiscountPoly) + ) { + (tierSpentUSD, tierPurchasedTokens, gotoNextTier) = _purchaseTierAmount(tierData.rate, tierData.tokenTotal.sub(tierData.mintedTotal), _investedUSD); + spentUSD = spentUSD.add(tierSpentUSD); + tokensMinted = tokensMinted.add(tierPurchasedTokens); } - return spentUSD; } function _purchaseTier( @@ -522,28 +641,44 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { uint256 _tierPrice, uint256 _tierRemaining, uint256 _investedUSD, - uint8 _tier + uint256 _tier ) internal - returns(uint256, uint256) + returns(uint256 spentUSD, uint256 purchasedTokens, bool gotoNextTier) { - uint256 maximumTokens = DecimalMath.div(_investedUSD, _tierPrice); - uint256 spentUSD; - uint256 purchasedTokens; - if (maximumTokens > _tierRemaining) { - spentUSD = DecimalMath.mul(_tierRemaining, _tierPrice); - // In case of rounding issues, ensure that spentUSD is never more than investedUSD - if (spentUSD > _investedUSD) { - spentUSD = _investedUSD; - } - purchasedTokens = _tierRemaining; + (spentUSD, purchasedTokens, gotoNextTier) = _purchaseTierAmount(_tierPrice, _tierRemaining, _investedUSD); + if (purchasedTokens > 0) { + require(ISecurityToken(securityToken).mint(_beneficiary, purchasedTokens), "Error in minting"); + emit TokenPurchase(msg.sender, _beneficiary, purchasedTokens, spentUSD, _tierPrice, _tier); + } + } + + function _purchaseTierAmount( + uint256 _tierPrice, + uint256 _tierRemaining, + uint256 _investedUSD + ) + internal + view + returns(uint256 spentUSD, uint256 purchasedTokens, bool gotoNextTier) + { + purchasedTokens = DecimalMath.div(_investedUSD, _tierPrice); + uint256 granularity = ISecurityToken(securityToken).granularity(); + + if (purchasedTokens > _tierRemaining) { + purchasedTokens = _tierRemaining.div(granularity); + gotoNextTier = true; } else { + purchasedTokens = purchasedTokens.div(granularity); + } + + purchasedTokens = purchasedTokens.mul(granularity); + spentUSD = DecimalMath.mul(purchasedTokens, _tierPrice); + + // In case of rounding issues, ensure that spentUSD is never more than investedUSD + if (spentUSD > _investedUSD) { spentUSD = _investedUSD; - purchasedTokens = maximumTokens; } - require(ISecurityToken(securityToken).mint(_beneficiary, purchasedTokens), "Error in minting"); - emit TokenPurchase(msg.sender, _beneficiary, purchasedTokens, spentUSD, _tierPrice, _tier); - return (spentUSD, purchasedTokens); } ///////////// @@ -576,15 +711,19 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { if (isFinalized) { return (finalAmountReturned == 0); } - return (mintedPerTierTotal[mintedPerTierTotal.length - 1] == tokensPerTierTotal[tokensPerTierTotal.length - 1]); + return (tiers[tiers.length - 1].mintedTotal == tiers[tiers.length - 1].tokenTotal); } + /** + * @dev returns current conversion rate of funds + * @param _fundRaiseType Fund raise type to get rate of + */ function getRate(FundRaiseType _fundRaiseType) public view returns (uint256) { if (_fundRaiseType == FundRaiseType.ETH) { return IOracle(_getOracle(bytes32("ETH"), bytes32("USD"))).getPrice(); } else if (_fundRaiseType == FundRaiseType.POLY) { return IOracle(_getOracle(bytes32("POLY"), bytes32("USD"))).getPrice(); - } else if (_fundRaiseType == FundRaiseType.DAI) { + } else if (_fundRaiseType == FundRaiseType.SC) { return 1 * 10**18; } else { revert("Incorrect funding"); @@ -597,7 +736,7 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @param _amount Value to convert to USD * @return uint256 Value in USD */ - function convertToUSD(FundRaiseType _fundRaiseType, uint256 _amount) public view returns(uint256) { + function convertToUSD(FundRaiseType _fundRaiseType, uint256 _amount) external view returns(uint256) { uint256 rate = getRate(_fundRaiseType); return DecimalMath.mul(_amount, rate); } @@ -608,7 +747,7 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { * @param _amount Value to convert from USD * @return uint256 Value in ETH or POLY */ - function convertFromUSD(FundRaiseType _fundRaiseType, uint256 _amount) public view returns(uint256) { + function convertFromUSD(FundRaiseType _fundRaiseType, uint256 _amount) external view returns(uint256) { uint256 rate = getRate(_fundRaiseType); return DecimalMath.div(_amount, rate); } @@ -630,30 +769,67 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { */ function getTokensMinted() public view returns (uint256) { uint256 tokensMinted; - for (uint8 i = 0; i < mintedPerTierTotal.length; i++) { - tokensMinted = tokensMinted.add(mintedPerTierTotal[i]); + for (uint256 i = 0; i < tiers.length; i++) { + tokensMinted = tokensMinted.add(tiers[i].mintedTotal); } return tokensMinted; } /** - * @notice Return the total no. of tokens sold for ETH + * @notice Return the total no. of tokens sold for the given fund raise type + * param _fundRaiseType The fund raising currency (e.g. ETH, POLY, SC) to calculate sold tokens for * @return uint256 Total number of tokens sold for ETH */ - function getTokensSoldFor(FundRaiseType _fundRaiseType) public view returns (uint256) { + function getTokensSoldFor(FundRaiseType _fundRaiseType) external view returns (uint256) { uint256 tokensSold; - for (uint8 i = 0; i < mintedPerTier[uint8(_fundRaiseType)].length; i++) { - tokensSold = tokensSold.add(mintedPerTier[uint8(_fundRaiseType)][i]); + for (uint256 i = 0; i < tiers.length; i++) { + tokensSold = tokensSold.add(tiers[i].minted[uint8(_fundRaiseType)]); } return tokensSold; } + /** + * @notice Return array of minted tokens in each fund raise type for given tier + * param _tier The tier to return minted tokens for + * @return uint256[] array of minted tokens in each fund raise type + */ + function getTokensMintedByTier(uint256 _tier) external view returns (uint256[]) { + require(_tier < tiers.length, "Invalid tier"); + uint256[] memory tokensMinted = new uint256[](3); + tokensMinted[0] = tiers[_tier].minted[uint8(FundRaiseType.ETH)]; + tokensMinted[1] = tiers[_tier].minted[uint8(FundRaiseType.POLY)]; + tokensMinted[2] = tiers[_tier].minted[uint8(FundRaiseType.SC)]; + return tokensMinted; + } + + /** + * @notice Return the total no. of tokens sold in a given tier + * param _tier The tier to calculate sold tokens for + * @return uint256 Total number of tokens sold in the tier + */ + function getTokensSoldByTier(uint256 _tier) external view returns (uint256) { + require(_tier < tiers.length, "Incorrect tier"); + uint256 tokensSold; + tokensSold = tokensSold.add(tiers[_tier].minted[uint8(FundRaiseType.ETH)]); + tokensSold = tokensSold.add(tiers[_tier].minted[uint8(FundRaiseType.POLY)]); + tokensSold = tokensSold.add(tiers[_tier].minted[uint8(FundRaiseType.SC)]); + return tokensSold; + } + /** * @notice Return the total no. of tiers * @return uint256 Total number of tiers */ - function getNumberOfTiers() public view returns (uint256) { - return tokensPerTierTotal.length; + function getNumberOfTiers() external view returns (uint256) { + return tiers.length; + } + + /** + * @notice Return the usd tokens accepted by the STO + * @return address[] usd tokens + */ + function getUsdTokens() external view returns (address[]) { + return usdTokens; } /** @@ -664,12 +840,48 @@ contract USDTieredSTO is ISTO, ReentrancyGuard { return allPermissions; } + /** + * @notice Return the STO details + * @return Unixtimestamp at which offering gets start. + * @return Unixtimestamp at which offering ends. + * @return Currently active tier + * @return Array of Number of tokens this STO will be allowed to sell at different tiers. + * @return Array Rate at which tokens are sold at different tiers + * @return Amount of funds raised + * @return Number of individual investors this STO have. + * @return Amount of tokens sold. + * @return Array of bools to show if funding is allowed in ETH, POLY, SC respectively + */ + function getSTODetails() external view returns(uint256, uint256, uint256, uint256[], uint256[], uint256, uint256, uint256, bool[]) { + uint256[] memory cap = new uint256[](tiers.length); + uint256[] memory rate = new uint256[](tiers.length); + for(uint256 i = 0; i < tiers.length; i++) { + cap[i] = tiers[i].tokenTotal; + rate[i] = tiers[i].rate; + } + bool[] memory _fundRaiseTypes = new bool[](3); + _fundRaiseTypes[0] = fundRaiseTypes[uint8(FundRaiseType.ETH)]; + _fundRaiseTypes[1] = fundRaiseTypes[uint8(FundRaiseType.POLY)]; + _fundRaiseTypes[2] = fundRaiseTypes[uint8(FundRaiseType.SC)]; + return ( + startTime, + endTime, + currentTier, + cap, + rate, + fundsRaisedUSD, + investorCount, + getTokensSold(), + _fundRaiseTypes + ); + } + /** * @notice This function returns the signature of configure function * @return bytes4 Configure function signature */ function getInitFunction() public pure returns (bytes4) { - return 0xb0ff041e; + return 0xeac2f9e4; } function _getOracle(bytes32 _currency, bytes32 _denominatedCurrency) internal view returns (address) { diff --git a/contracts/modules/STO/USDTieredSTOFactory.sol b/contracts/modules/STO/USDTieredSTOFactory.sol index adc3ba628..1e44c7a7e 100644 --- a/contracts/modules/STO/USDTieredSTOFactory.sol +++ b/contracts/modules/STO/USDTieredSTOFactory.sol @@ -1,6 +1,7 @@ pragma solidity ^0.4.24; -import "../../interfaces/IUSDTieredSTOProxy.sol"; +import "../../interfaces/IBoot.sol"; +import "../../proxy/USDTieredSTOProxy.sol"; import "../ModuleFactory.sol"; import "../../libraries/Util.sol"; @@ -9,18 +10,18 @@ import "../../libraries/Util.sol"; */ contract USDTieredSTOFactory is ModuleFactory { - address public USDTieredSTOProxyAddress; + address public logicContract; /** * @notice Constructor * @param _polyAddress Address of the polytoken */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _proxyFactoryAddress) public + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - require(_proxyFactoryAddress != address(0), "0x address is not allowed"); - USDTieredSTOProxyAddress = _proxyFactoryAddress; - version = "1.0.0"; + require(_logicContract != address(0), "0x address is not allowed"); + logicContract = _logicContract; + version = "2.1.0"; name = "USDTieredSTO"; title = "USD Tiered STO"; /*solium-disable-next-line max-len*/ @@ -36,16 +37,15 @@ contract USDTieredSTOFactory is ModuleFactory { function deploy(bytes _data) external returns(address) { if(setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "Sufficent Allowance is not provided"); - require(USDTieredSTOProxyAddress != address(0), "Proxy contract should be pre-set"); //Check valid bytes - can only call module init function - address usdTieredSTO = IUSDTieredSTOProxy(USDTieredSTOProxyAddress).deploySTO(msg.sender, address(polyToken), address(this)); + address usdTieredSTO = new USDTieredSTOProxy(msg.sender, address(polyToken), logicContract); //Checks that _data is valid (not calling anything it shouldn't) - require(Util.getSig(_data) == IUSDTieredSTOProxy(USDTieredSTOProxyAddress).getInitFunction(usdTieredSTO), "Invalid data"); + require(Util.getSig(_data) == IBoot(usdTieredSTO).getInitFunction(), "Invalid data"); /*solium-disable-next-line security/no-low-level-calls*/ - require(address(usdTieredSTO).call(_data), "Unsuccessfull call"); + require(usdTieredSTO.call(_data), "Unsuccessfull call"); /*solium-disable-next-line security/no-block-members*/ emit GenerateModuleFromFactory(usdTieredSTO, getName(), address(this), msg.sender, setupCost, now); - return address(usdTieredSTO); + return usdTieredSTO; } /** diff --git a/contracts/modules/TransferManager/CountTransferManager.sol b/contracts/modules/TransferManager/CountTransferManager.sol index b54870251..86a537ebe 100644 --- a/contracts/modules/TransferManager/CountTransferManager.sol +++ b/contracts/modules/TransferManager/CountTransferManager.sol @@ -26,12 +26,14 @@ contract CountTransferManager is ITransferManager { } /** @notice Used to verify the transfer transaction and prevent a transfer if it passes the allowed amount of token holders + * @param _from Address of the sender * @param _to Address of the receiver + * @param _amount Amount to send */ function verifyTransfer( - address /* _from */, + address _from, address _to, - uint256 /* _amount */, + uint256 _amount, bytes /* _data */, bool /* _isTransfer */ ) @@ -41,7 +43,7 @@ contract CountTransferManager is ITransferManager { if (!paused) { if (maxHolderCount < ISecurityToken(securityToken).getInvestorCount()) { // Allow transfers to existing maxHolders - if (ISecurityToken(securityToken).balanceOf(_to) != 0) { + if (ISecurityToken(securityToken).balanceOf(_to) != 0 || ISecurityToken(securityToken).balanceOf(_from) == _amount) { return Result.NA; } return Result.INVALID; diff --git a/contracts/modules/TransferManager/CountTransferManagerFactory.sol b/contracts/modules/TransferManager/CountTransferManagerFactory.sol index d9b1328f7..c42cebc61 100644 --- a/contracts/modules/TransferManager/CountTransferManagerFactory.sol +++ b/contracts/modules/TransferManager/CountTransferManagerFactory.sol @@ -16,7 +16,7 @@ contract CountTransferManagerFactory is ModuleFactory { constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - version = "1.0.0"; + version = "2.1.0"; name = "CountTransferManager"; title = "Count Transfer Manager"; description = "Restrict the number of investors"; diff --git a/contracts/modules/TransferManager/GeneralTransferManager.sol b/contracts/modules/TransferManager/GeneralTransferManager.sol index 814d54d7b..0d2f96700 100644 --- a/contracts/modules/TransferManager/GeneralTransferManager.sol +++ b/contracts/modules/TransferManager/GeneralTransferManager.sol @@ -1,47 +1,16 @@ pragma solidity ^0.4.24; import "./ITransferManager.sol"; +import "../../storage/GeneralTransferManagerStorage.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** * @title Transfer Manager module for core transfer validation functionality */ -contract GeneralTransferManager is ITransferManager { +contract GeneralTransferManager is GeneralTransferManagerStorage, ITransferManager { using SafeMath for uint256; - //Address from which issuances come - address public issuanceAddress = address(0); - - //Address which can sign whitelist changes - address public signingAddress = address(0); - - bytes32 public constant WHITELIST = "WHITELIST"; - bytes32 public constant FLAGS = "FLAGS"; - - //from and to timestamps that an investor can send / receive tokens respectively - struct TimeRestriction { - uint256 fromTime; - uint256 toTime; - uint256 expiryTime; - bool canBuyFromSTO; - } - - // An address can only send / receive tokens once their corresponding uint256 > block.number - // (unless allowAllTransfers == true or allowAllWhitelistTransfers == true) - mapping (address => TimeRestriction) public whitelist; - // Map of used nonces by customer - mapping(address => mapping(uint256 => bool)) public nonceMap; - - //If true, there are no transfer restrictions, for any addresses - bool public allowAllTransfers = false; - //If true, time lock is ignored for transfers (address must still be on whitelist) - bool public allowAllWhitelistTransfers = false; - //If true, time lock is ignored for issuances (address must still be on whitelist) - bool public allowAllWhitelistIssuances = true; - //If true, time lock is ignored for burn transactions - bool public allowAllBurnTransfers = false; - // Emit when Issuance address get changed event ChangeIssuanceAddress(address _issuanceAddress); // Emit when there is change in the flag variable called allowAllTransfers @@ -55,12 +24,19 @@ contract GeneralTransferManager is ITransferManager { // Emit when there is change in the flag variable called signingAddress event ChangeSigningAddress(address _signingAddress); // Emit when investor details get modified related to their whitelisting + event ChangeDefaults(uint64 _defaultCanSendAfter, uint64 _defaultCanReceiveAfter); + + // _canSendAfter is the time from which the _investor can send tokens + // _canReceiveAfter is the time from which the _investor can receive tokens + // if allowAllWhitelistIssuances is TRUE, then _canReceiveAfter is ignored when receiving tokens from the issuance address + // if allowAllWhitelistTransfers is TRUE, then _canReceiveAfter and _canSendAfter is ignored when sending or receiving tokens + // in any case, any investor sending or receiving tokens, must have a _expiryTime in the future event ModifyWhitelist( - address _investor, + address indexed _investor, uint256 _dateAdded, - address _addedBy, - uint256 _fromTime, - uint256 _toTime, + address indexed _addedBy, + uint256 _canSendAfter, + uint256 _canReceiveAfter, uint256 _expiryTime, bool _canBuyFromSTO ); @@ -83,11 +59,26 @@ contract GeneralTransferManager is ITransferManager { return bytes4(0); } + /** + * @notice Used to change the default times used when canSendAfter / canReceiveAfter are zero + * @param _defaultCanSendAfter default for zero canSendAfter + * @param _defaultCanReceiveAfter default for zero canReceiveAfter + */ + function changeDefaults(uint64 _defaultCanSendAfter, uint64 _defaultCanReceiveAfter) public withPerm(FLAGS) { + /* 0 values are also allowed as they represent that the Issuer + does not want a default value for these variables. + 0 is also the default value of these variables */ + defaults.canSendAfter = _defaultCanSendAfter; + defaults.canReceiveAfter = _defaultCanReceiveAfter; + emit ChangeDefaults(_defaultCanSendAfter, _defaultCanReceiveAfter); + } + /** * @notice Used to change the Issuance Address * @param _issuanceAddress new address for the issuance */ function changeIssuanceAddress(address _issuanceAddress) public withPerm(FLAGS) { + // address(0x0) is also a valid value and in most cases, the address that issues tokens is 0x0. issuanceAddress = _issuanceAddress; emit ChangeIssuanceAddress(_issuanceAddress); } @@ -97,6 +88,9 @@ contract GeneralTransferManager is ITransferManager { * @param _signingAddress new address for the signing */ function changeSigningAddress(address _signingAddress) public withPerm(FLAGS) { + /* address(0x0) is also a valid value as an Issuer might want to + give this permission to nobody (except their own address). + 0x0 is also the default value */ signingAddress = _signingAddress; emit ChangeSigningAddress(_signingAddress); } @@ -168,16 +162,25 @@ contract GeneralTransferManager is ITransferManager { //Anyone on the whitelist can transfer, regardless of time return (_onWhitelist(_to) && _onWhitelist(_from)) ? Result.VALID : Result.NA; } - if (allowAllWhitelistIssuances && _from == issuanceAddress) { - if (!whitelist[_to].canBuyFromSTO && _isSTOAttached()) { + + (uint64 adjustedCanSendAfter, uint64 adjustedCanReceiveAfter) = _adjustTimes(whitelist[_from].canSendAfter, whitelist[_to].canReceiveAfter); + if (_from == issuanceAddress) { + // Possible STO transaction, but investor not allowed to purchased from STO + if ((whitelist[_to].canBuyFromSTO == 0) && _isSTOAttached()) { return Result.NA; } - return _onWhitelist(_to) ? Result.VALID : Result.NA; + // if allowAllWhitelistIssuances is true, so time stamp ignored + if (allowAllWhitelistIssuances) { + return _onWhitelist(_to) ? Result.VALID : Result.NA; + } else { + return (_onWhitelist(_to) && (adjustedCanReceiveAfter <= uint64(now))) ? Result.VALID : Result.NA; + } } + //Anyone on the whitelist can transfer provided the blocknumber is large enough /*solium-disable-next-line security/no-block-members*/ - return ((_onWhitelist(_from) && whitelist[_from].fromTime <= now) && - (_onWhitelist(_to) && whitelist[_to].toTime <= now)) ? Result.VALID : Result.NA; /*solium-disable-line security/no-block-members*/ + return ((_onWhitelist(_from) && (adjustedCanSendAfter <= uint64(now))) && + (_onWhitelist(_to) && (adjustedCanReceiveAfter <= uint64(now)))) ? Result.VALID : Result.NA; /*solium-disable-line security/no-block-members*/ } return Result.NA; } @@ -185,56 +188,88 @@ contract GeneralTransferManager is ITransferManager { /** * @notice Adds or removes addresses from the whitelist. * @param _investor is the address to whitelist - * @param _fromTime is the moment when the sale lockup period ends and the investor can freely sell his tokens - * @param _toTime is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _canSendAfter the moment when the sale lockup period ends and the investor can freely sell or transfer away their tokens + * @param _canReceiveAfter the moment when the purchase lockup period ends and the investor can freely purchase or receive from others * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not. */ function modifyWhitelist( address _investor, - uint256 _fromTime, - uint256 _toTime, + uint256 _canSendAfter, + uint256 _canReceiveAfter, uint256 _expiryTime, bool _canBuyFromSTO ) public withPerm(WHITELIST) { - //Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist - whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO); - /*solium-disable-next-line security/no-block-members*/ - emit ModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime, _expiryTime, _canBuyFromSTO); + _modifyWhitelist(_investor, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO); + } + + /** + * @notice Adds or removes addresses from the whitelist. + * @param _investor is the address to whitelist + * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC + * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not. + */ + function _modifyWhitelist( + address _investor, + uint256 _canSendAfter, + uint256 _canReceiveAfter, + uint256 _expiryTime, + bool _canBuyFromSTO + ) + internal + { + require(_investor != address(0), "Invalid investor"); + uint8 canBuyFromSTO = 0; + if (_canBuyFromSTO) { + canBuyFromSTO = 1; + } + if (whitelist[_investor].added == uint8(0)) { + investors.push(_investor); + } + require( + uint64(_canSendAfter) == _canSendAfter && + uint64(_canReceiveAfter) == _canReceiveAfter && + uint64(_expiryTime) == _expiryTime, + "uint64 overflow" + ); + whitelist[_investor] = TimeRestriction(uint64(_canSendAfter), uint64(_canReceiveAfter), uint64(_expiryTime), canBuyFromSTO, uint8(1)); + emit ModifyWhitelist(_investor, now, msg.sender, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO); } /** * @notice Adds or removes addresses from the whitelist. * @param _investors List of the addresses to whitelist - * @param _fromTimes An array of the moment when the sale lockup period ends and the investor can freely sell his tokens - * @param _toTimes An array of the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _canSendAfters An array of the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _canReceiveAfters An array of the moment when the purchase lockup period ends and the investor can freely purchase tokens from others * @param _expiryTimes An array of the moment till investors KYC will be validated. After that investor need to do re-KYC * @param _canBuyFromSTO An array of boolean values */ function modifyWhitelistMulti( address[] _investors, - uint256[] _fromTimes, - uint256[] _toTimes, + uint256[] _canSendAfters, + uint256[] _canReceiveAfters, uint256[] _expiryTimes, bool[] _canBuyFromSTO ) public withPerm(WHITELIST) { - require(_investors.length == _fromTimes.length, "Mismatched input lengths"); - require(_fromTimes.length == _toTimes.length, "Mismatched input lengths"); - require(_toTimes.length == _expiryTimes.length, "Mismatched input lengths"); - require(_canBuyFromSTO.length == _toTimes.length, "Mismatched input length"); + require(_investors.length == _canSendAfters.length, "Mismatched input lengths"); + require(_canSendAfters.length == _canReceiveAfters.length, "Mismatched input lengths"); + require(_canReceiveAfters.length == _expiryTimes.length, "Mismatched input lengths"); + require(_canBuyFromSTO.length == _canReceiveAfters.length, "Mismatched input length"); for (uint256 i = 0; i < _investors.length; i++) { - modifyWhitelist(_investors[i], _fromTimes[i], _toTimes[i], _expiryTimes[i], _canBuyFromSTO[i]); + _modifyWhitelist(_investors[i], _canSendAfters[i], _canReceiveAfters[i], _expiryTimes[i], _canBuyFromSTO[i]); } } /** * @notice Adds or removes addresses from the whitelist - can be called by anyone with a valid signature * @param _investor is the address to whitelist - * @param _fromTime is the moment when the sale lockup period ends and the investor can freely sell his tokens - * @param _toTime is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others + * @param _canSendAfter is the moment when the sale lockup period ends and the investor can freely sell his tokens + * @param _canReceiveAfter is the moment when the purchase lockup period ends and the investor can freely purchase tokens from others * @param _expiryTime is the moment till investors KYC will be validated. After that investor need to do re-KYC * @param _canBuyFromSTO is used to know whether the investor is restricted investor or not. * @param _validFrom is the time that this signature is valid from @@ -246,8 +281,8 @@ contract GeneralTransferManager is ITransferManager { */ function modifyWhitelistSigned( address _investor, - uint256 _fromTime, - uint256 _toTime, + uint256 _canSendAfter, + uint256 _canReceiveAfter, uint256 _expiryTime, bool _canBuyFromSTO, uint256 _validFrom, @@ -264,13 +299,10 @@ contract GeneralTransferManager is ITransferManager { require(!nonceMap[_investor][_nonce], "Already used signature"); nonceMap[_investor][_nonce] = true; bytes32 hash = keccak256( - abi.encodePacked(this, _investor, _fromTime, _toTime, _expiryTime, _canBuyFromSTO, _validFrom, _validTo, _nonce) + abi.encodePacked(this, _investor, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO, _validFrom, _validTo, _nonce) ); _checkSig(hash, _v, _r, _s); - //Passing a _time == 0 into this function, is equivalent to removing the _investor from the whitelist - whitelist[_investor] = TimeRestriction(_fromTime, _toTime, _expiryTime, _canBuyFromSTO); - /*solium-disable-next-line security/no-block-members*/ - emit ModifyWhitelist(_investor, now, msg.sender, _fromTime, _toTime, _expiryTime, _canBuyFromSTO); + _modifyWhitelist(_investor, _canSendAfter, _canReceiveAfter, _expiryTime, _canBuyFromSTO); } /** @@ -278,7 +310,7 @@ contract GeneralTransferManager is ITransferManager { */ function _checkSig(bytes32 _hash, uint8 _v, bytes32 _r, bytes32 _s) internal view { //Check that the signature is valid - //sig should be signing - _investor, _fromTime, _toTime & _expiryTime and be signed by the issuer address + //sig should be signing - _investor, _canSendAfter, _canReceiveAfter & _expiryTime and be signed by the issuer address address signer = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash)), _v, _r, _s); require(signer == Ownable(securityToken).owner() || signer == signingAddress, "Incorrect signer"); } @@ -289,8 +321,7 @@ contract GeneralTransferManager is ITransferManager { * @param _investor Address of the investor */ function _onWhitelist(address _investor) internal view returns(bool) { - return (((whitelist[_investor].fromTime != 0) || (whitelist[_investor].toTime != 0)) && - (whitelist[_investor].expiryTime >= now)); /*solium-disable-line security/no-block-members*/ + return (whitelist[_investor].expiryTime >= uint64(now)); /*solium-disable-line security/no-block-members*/ } /** @@ -301,6 +332,63 @@ contract GeneralTransferManager is ITransferManager { return attached; } + /** + * @notice Internal function to adjust times using default values + */ + function _adjustTimes(uint64 _canSendAfter, uint64 _canReceiveAfter) internal view returns(uint64, uint64) { + uint64 adjustedCanSendAfter = _canSendAfter; + uint64 adjustedCanReceiveAfter = _canReceiveAfter; + if (_canSendAfter == 0) { + adjustedCanSendAfter = defaults.canSendAfter; + } + if (_canReceiveAfter == 0) { + adjustedCanReceiveAfter = defaults.canReceiveAfter; + } + return (adjustedCanSendAfter, adjustedCanReceiveAfter); + } + + /** + * @dev Returns list of all investors + */ + function getInvestors() external view returns(address[]) { + return investors; + } + + /** + * @dev Returns list of all investors data + */ + function getAllInvestorsData() external view returns(address[], uint256[], uint256[], uint256[], bool[]) { + (uint256[] memory canSendAfters, uint256[] memory canReceiveAfters, uint256[] memory expiryTimes, bool[] memory canBuyFromSTOs) + = _investorsData(investors); + return (investors, canSendAfters, canReceiveAfters, expiryTimes, canBuyFromSTOs); + + } + + /** + * @dev Returns list of specified investors data + */ + function getInvestorsData(address[] _investors) external view returns(uint256[], uint256[], uint256[], bool[]) { + return _investorsData(_investors); + } + + function _investorsData(address[] _investors) internal view returns(uint256[], uint256[], uint256[], bool[]) { + uint256[] memory canSendAfters = new uint256[](_investors.length); + uint256[] memory canReceiveAfters = new uint256[](_investors.length); + uint256[] memory expiryTimes = new uint256[](_investors.length); + bool[] memory canBuyFromSTOs = new bool[](_investors.length); + for (uint256 i = 0; i < _investors.length; i++) { + canSendAfters[i] = whitelist[_investors[i]].canSendAfter; + canReceiveAfters[i] = whitelist[_investors[i]].canReceiveAfter; + expiryTimes[i] = whitelist[_investors[i]].expiryTime; + if (whitelist[_investors[i]].canBuyFromSTO == 0) { + canBuyFromSTOs[i] = false; + } else { + canBuyFromSTOs[i] = true; + } + } + return (canSendAfters, canReceiveAfters, expiryTimes, canBuyFromSTOs); + } + /** * @notice Return the permissions flag that are associated with general trnasfer manager */ diff --git a/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol b/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol index c15eb52aa..cffc61a32 100644 --- a/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol +++ b/contracts/modules/TransferManager/GeneralTransferManagerFactory.sol @@ -1,6 +1,6 @@ pragma solidity ^0.4.24; -import "./GeneralTransferManager.sol"; +import "../../proxy/GeneralTransferManagerProxy.sol"; import "../ModuleFactory.sol"; /** @@ -8,19 +8,27 @@ import "../ModuleFactory.sol"; */ contract GeneralTransferManagerFactory is ModuleFactory { + address public logicContract; + /** * @notice Constructor * @param _polyAddress Address of the polytoken + * @param _setupCost Setup cost of the module + * @param _usageCost Usage cost of the module + * @param _subscriptionCost Subscription cost of the module + * @param _logicContract Contract address that contains the logic related to `description` */ - constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public + constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost, address _logicContract) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - version = "1.0.0"; + require(_logicContract != address(0), "Invalid logic contract"); + version = "2.1.0"; name = "GeneralTransferManager"; title = "General Transfer Manager"; description = "Manage transfers using a time based whitelist"; compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); + logicContract = _logicContract; } @@ -31,7 +39,7 @@ contract GeneralTransferManagerFactory is ModuleFactory { function deploy(bytes /* _data */) external returns(address) { if (setupCost > 0) require(polyToken.transferFrom(msg.sender, owner, setupCost), "Failed transferFrom because of sufficent Allowance is not provided"); - address generalTransferManager = new GeneralTransferManager(msg.sender, address(polyToken)); + address generalTransferManager = new GeneralTransferManagerProxy(msg.sender, address(polyToken), logicContract); /*solium-disable-next-line security/no-block-members*/ emit GenerateModuleFromFactory(address(generalTransferManager), getName(), address(this), msg.sender, setupCost, now); return address(generalTransferManager); diff --git a/contracts/modules/TransferManager/ManualApprovalTransferManager.sol b/contracts/modules/TransferManager/ManualApprovalTransferManager.sol index d95241e9a..5e95f6513 100644 --- a/contracts/modules/TransferManager/ManualApprovalTransferManager.sol +++ b/contracts/modules/TransferManager/ManualApprovalTransferManager.sol @@ -4,49 +4,45 @@ import "./ITransferManager.sol"; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; /** - * @title Transfer Manager module for manually approving or blocking transactions between accounts + * @title Transfer Manager module for manually approving transactions between accounts */ contract ManualApprovalTransferManager is ITransferManager { using SafeMath for uint256; - //Address from which issuances come - address public issuanceAddress = address(0); - - //Address which can sign whitelist changes - address public signingAddress = address(0); - bytes32 public constant TRANSFER_APPROVAL = "TRANSFER_APPROVAL"; //Manual approval is an allowance (that has been approved) with an expiry time struct ManualApproval { + address from; + address to; uint256 allowance; uint256 expiryTime; + bytes32 description; } - //Manual blocking allows you to specify a list of blocked address pairs with an associated expiry time for the block - struct ManualBlocking { - uint256 expiryTime; - } - - //Store mappings of address => address with ManualApprovals - mapping (address => mapping (address => ManualApproval)) public manualApprovals; + mapping (address => mapping (address => uint256)) public approvalIndex; - //Store mappings of address => address with ManualBlockings - mapping (address => mapping (address => ManualBlocking)) public manualBlockings; + // An array to track all approvals. It is an unbounded array but it's not a problem as + // it is never looped through in an onchain call. It is defined as an Array instead of mapping + // just to make it easier for users to fetch list of all approvals through constant functions. + ManualApproval[] public approvals; event AddManualApproval( address indexed _from, address indexed _to, uint256 _allowance, uint256 _expiryTime, + bytes32 _description, address indexed _addedBy ); - event AddManualBlocking( + event ModifyManualApproval( address indexed _from, address indexed _to, uint256 _expiryTime, - address indexed _addedBy + uint256 _allowance, + bytes32 _description, + address indexed _edittedBy ); event RevokeManualApproval( @@ -55,12 +51,6 @@ contract ManualApprovalTransferManager is ITransferManager { address indexed _addedBy ); - event RevokeManualBlocking( - address indexed _from, - address indexed _to, - address indexed _addedBy - ); - /** * @notice Constructor * @param _securityToken Address of the security token @@ -79,7 +69,8 @@ contract ManualApprovalTransferManager is ITransferManager { return bytes4(0); } - /** @notice Used to verify the transfer transaction and allow a manually approved transqaction to bypass other restrictions + /** + * @notice Used to verify the transfer transaction and allow a manually approved transqaction to bypass other restrictions * @param _from Address of the sender * @param _to Address of the receiver * @param _amount The amount of tokens to transfer @@ -88,16 +79,14 @@ contract ManualApprovalTransferManager is ITransferManager { function verifyTransfer(address _from, address _to, uint256 _amount, bytes /* _data */, bool _isTransfer) public returns(Result) { // function must only be called by the associated security token if _isTransfer == true require(_isTransfer == false || msg.sender == securityToken, "Sender is not the owner"); - // manual blocking takes precidence over manual approval - if (!paused) { - /*solium-disable-next-line security/no-block-members*/ - if (manualBlockings[_from][_to].expiryTime >= now) { - return Result.INVALID; - } - /*solium-disable-next-line security/no-block-members*/ - if ((manualApprovals[_from][_to].expiryTime >= now) && (manualApprovals[_from][_to].allowance >= _amount)) { + uint256 index = approvalIndex[_from][_to]; + if (!paused && index != 0) { + index--; //Actual index is stored index - 1. + ManualApproval storage approval = approvals[index]; + uint256 allowance = approval.allowance; + if ((approval.expiryTime >= now) && (allowance >= _amount)) { if (_isTransfer) { - manualApprovals[_from][_to].allowance = manualApprovals[_from][_to].allowance.sub(_amount); + approval.allowance = allowance - _amount; } return Result.VALID; } @@ -111,29 +100,157 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _to is the address to which transfers are approved * @param _allowance is the approved amount of tokens * @param _expiryTime is the time until which the transfer is allowed + * @param _description Description about the manual approval */ - function addManualApproval(address _from, address _to, uint256 _allowance, uint256 _expiryTime) public withPerm(TRANSFER_APPROVAL) { + function addManualApproval( + address _from, + address _to, + uint256 _allowance, + uint256 _expiryTime, + bytes32 _description + ) + external + withPerm(TRANSFER_APPROVAL) + { + _addManualApproval(_from, _to, _allowance, _expiryTime, _description); + } + + function _addManualApproval(address _from, address _to, uint256 _allowance, uint256 _expiryTime, bytes32 _description) internal { require(_to != address(0), "Invalid to address"); - /*solium-disable-next-line security/no-block-members*/ require(_expiryTime > now, "Invalid expiry time"); - require(manualApprovals[_from][_to].allowance == 0, "Approval already exists"); - manualApprovals[_from][_to] = ManualApproval(_allowance, _expiryTime); - emit AddManualApproval(_from, _to, _allowance, _expiryTime, msg.sender); + require(_allowance > 0, "Invalid allowance"); + uint256 index = approvalIndex[_from][_to]; + if (index != 0) { + index--; //Actual index is stored index - 1. + require(approvals[index].expiryTime < now || approvals[index].allowance == 0, "Approval already exists"); + _revokeManualApproval(_from, _to); + } + approvals.push(ManualApproval(_from, _to, _allowance, _expiryTime, _description)); + approvalIndex[_from][_to] = approvals.length; + emit AddManualApproval(_from, _to, _allowance, _expiryTime, _description, msg.sender); } /** - * @notice Adds a pair of addresses to manual blockings - * @param _from is the address from which transfers are blocked - * @param _to is the address to which transfers are blocked - * @param _expiryTime is the time until which the transfer is blocked + * @notice Adds mutiple manual approvals in batch + * @param _from is the address array from which transfers are approved + * @param _to is the address array to which transfers are approved + * @param _allowances is the array of approved amounts + * @param _expiryTimes is the array of the times until which eath transfer is allowed + * @param _descriptions is the description array for these manual approvals */ - function addManualBlocking(address _from, address _to, uint256 _expiryTime) public withPerm(TRANSFER_APPROVAL) { + function addManualApprovalMulti( + address[] _from, + address[] _to, + uint256[] _allowances, + uint256[] _expiryTimes, + bytes32[] _descriptions + ) + external + withPerm(TRANSFER_APPROVAL) + { + _checkInputLengthArray(_from, _to, _allowances, _expiryTimes, _descriptions); + for (uint256 i = 0; i < _from.length; i++){ + _addManualApproval(_from[i], _to[i], _allowances[i], _expiryTimes[i], _descriptions[i]); + } + } + + /** + * @notice Modify the existing manual approvals + * @param _from is the address from which transfers are approved + * @param _to is the address to which transfers are approved + * @param _expiryTime is the time until which the transfer is allowed + * @param _changeInAllowance is the change in allowance + * @param _description Description about the manual approval + * @param _increase tells whether the allowances will be increased (true) or decreased (false). + * or any value when there is no change in allowances + */ + function modifyManualApproval( + address _from, + address _to, + uint256 _expiryTime, + uint256 _changeInAllowance, + bytes32 _description, + bool _increase + ) + external + withPerm(TRANSFER_APPROVAL) + { + _modifyManualApproval(_from, _to, _expiryTime, _changeInAllowance, _description, _increase); + } + + function _modifyManualApproval( + address _from, + address _to, + uint256 _expiryTime, + uint256 _changeInAllowance, + bytes32 _description, + bool _increase + ) + internal + { require(_to != address(0), "Invalid to address"); /*solium-disable-next-line security/no-block-members*/ require(_expiryTime > now, "Invalid expiry time"); - require(manualBlockings[_from][_to].expiryTime == 0, "Blocking already exists"); - manualBlockings[_from][_to] = ManualBlocking(_expiryTime); - emit AddManualBlocking(_from, _to, _expiryTime, msg.sender); + uint256 index = approvalIndex[_from][_to]; + require(index != 0, "Approval not present"); + index--; //Index is stored in an incremented form. 0 represnts non existant. + ManualApproval storage approval = approvals[index]; + uint256 allowance = approval.allowance; + uint256 expiryTime = approval.expiryTime; + require(allowance != 0 && expiryTime > now, "Not allowed"); + + if (_changeInAllowance > 0) { + if (_increase) { + // Allowance get increased + allowance = allowance.add(_changeInAllowance); + } else { + // Allowance get decreased + if (_changeInAllowance >= allowance) { + allowance = 0; + } else { + allowance = allowance - _changeInAllowance; + } + } + approval.allowance = allowance; + } + + // Greedy storage technique + if (expiryTime != _expiryTime) { + approval.expiryTime = _expiryTime; + } + if (approval.description != _description) { + approval.description = _description; + } + + emit ModifyManualApproval(_from, _to, _expiryTime, allowance, _description, msg.sender); + } + + /** + * @notice Adds mutiple manual approvals in batch + * @param _from is the address array from which transfers are approved + * @param _to is the address array to which transfers are approved + * @param _expiryTimes is the array of the times until which eath transfer is allowed + * @param _changedAllowances is the array of approved amounts + * @param _descriptions is the description array for these manual approvals + * @param _increase Array of bool values which tells whether the allowances will be increased (true) or decreased (false) + * or any value when there is no change in allowances + */ + function modifyManualApprovalMulti( + address[] _from, + address[] _to, + uint256[] _expiryTimes, + uint256[] _changedAllowances, + bytes32[] _descriptions, + bool[] _increase + ) + public + withPerm(TRANSFER_APPROVAL) + { + _checkInputLengthArray(_from, _to, _changedAllowances, _expiryTimes, _descriptions); + require(_increase.length == _changedAllowances.length, "Input length array mismatch"); + for (uint256 i = 0; i < _from.length; i++) { + _modifyManualApproval(_from[i], _to[i], _expiryTimes[i], _changedAllowances[i], _descriptions[i], _increase[i]); + } } /** @@ -141,21 +258,154 @@ contract ManualApprovalTransferManager is ITransferManager { * @param _from is the address from which transfers are approved * @param _to is the address to which transfers are approved */ - function revokeManualApproval(address _from, address _to) public withPerm(TRANSFER_APPROVAL) { - require(_to != address(0), "Invalid to address"); - delete manualApprovals[_from][_to]; + function revokeManualApproval(address _from, address _to) external withPerm(TRANSFER_APPROVAL) { + _revokeManualApproval(_from, _to); + } + + function _revokeManualApproval(address _from, address _to) internal { + uint256 index = approvalIndex[_from][_to]; + require(index != 0, "Approval does not exist"); + index--; //Actual index is stored index - 1. + uint256 lastApprovalIndex = approvals.length - 1; + // find the record in active approvals array & delete it + if (index != lastApprovalIndex) { + approvals[index] = approvals[lastApprovalIndex]; + approvalIndex[approvals[index].from][approvals[index].to] = index + 1; + } + delete approvalIndex[_from][_to]; + approvals.length--; emit RevokeManualApproval(_from, _to, msg.sender); } /** - * @notice Removes a pairs of addresses from manual approvals - * @param _from is the address from which transfers are approved - * @param _to is the address to which transfers are approved + * @notice Removes mutiple pairs of addresses from manual approvals + * @param _from is the address array from which transfers are approved + * @param _to is the address array to which transfers are approved */ - function revokeManualBlocking(address _from, address _to) public withPerm(TRANSFER_APPROVAL) { - require(_to != address(0), "Invalid to address"); - delete manualBlockings[_from][_to]; - emit RevokeManualBlocking(_from, _to, msg.sender); + function revokeManualApprovalMulti(address[] _from, address[] _to) external withPerm(TRANSFER_APPROVAL) { + require(_from.length == _to.length, "Input array length mismatch"); + for(uint256 i = 0; i < _from.length; i++){ + _revokeManualApproval(_from[i], _to[i]); + } + } + + function _checkInputLengthArray( + address[] _from, + address[] _to, + uint256[] _expiryTimes, + uint256[] _allowances, + bytes32[] _descriptions + ) + internal + pure + { + require(_from.length == _to.length && + _to.length == _allowances.length && + _allowances.length == _expiryTimes.length && + _expiryTimes.length == _descriptions.length, + "Input array length mismatch" + ); + } + + /** + * @notice Returns the all active approvals corresponds to an address + * @param _user Address of the holder corresponds to whom list of manual approvals + * need to return + * @return address[] addresses from + * @return address[] addresses to + * @return uint256[] allowances provided to the approvals + * @return uint256[] expiry times provided to the approvals + * @return bytes32[] descriptions provided to the approvals + */ + function getActiveApprovalsToUser(address _user) external view returns(address[], address[], uint256[], uint256[], bytes32[]) { + uint256 counter = 0; + uint256 approvalsLength = approvals.length; + for (uint256 i = 0; i < approvalsLength; i++) { + if ((approvals[i].from == _user || approvals[i].to == _user) + && approvals[i].expiryTime >= now) + counter ++; + } + + address[] memory from = new address[](counter); + address[] memory to = new address[](counter); + uint256[] memory allowance = new uint256[](counter); + uint256[] memory expiryTime = new uint256[](counter); + bytes32[] memory description = new bytes32[](counter); + + counter = 0; + for (i = 0; i < approvalsLength; i++) { + if ((approvals[i].from == _user || approvals[i].to == _user) + && approvals[i].expiryTime >= now) { + + from[counter]=approvals[i].from; + to[counter]=approvals[i].to; + allowance[counter]=approvals[i].allowance; + expiryTime[counter]=approvals[i].expiryTime; + description[counter]=approvals[i].description; + counter ++; + } + } + return (from, to, allowance, expiryTime, description); + } + + /** + * @notice Get the details of the approval corresponds to _from & _to addresses + * @param _from Address of the sender + * @param _to Address of the receiver + * @return uint256 expiryTime of the approval + * @return uint256 allowance provided to the approval + * @return uint256 Description provided to the approval + */ + function getApprovalDetails(address _from, address _to) external view returns(uint256, uint256, bytes32) { + uint256 index = approvalIndex[_from][_to]; + if (index != 0) { + index--; + if (index < approvals.length) { + ManualApproval storage approval = approvals[index]; + return( + approval.expiryTime, + approval.allowance, + approval.description + ); + } + } + } + + /** + * @notice Returns the current number of active approvals + */ + function getTotalApprovalsLength() external view returns(uint256) { + return approvals.length; + } + + /** + * @notice Get the details of all approvals + * @return address[] addresses from + * @return address[] addresses to + * @return uint256[] allowances provided to the approvals + * @return uint256[] expiry times provided to the approvals + * @return bytes32[] descriptions provided to the approvals + */ + function getAllApprovals() external view returns(address[], address[], uint256[], uint256[], bytes32[]) { + address[] memory from = new address[](approvals.length); + address[] memory to = new address[](approvals.length); + uint256[] memory allowance = new uint256[](approvals.length); + uint256[] memory expiryTime = new uint256[](approvals.length); + bytes32[] memory description = new bytes32[](approvals.length); + uint256 approvalsLength = approvals.length; + + for (uint256 i = 0; i < approvalsLength; i++) { + + from[i]=approvals[i].from; + to[i]=approvals[i].to; + allowance[i]=approvals[i].allowance; + expiryTime[i]=approvals[i].expiryTime; + description[i]=approvals[i].description; + + } + + return (from, to, allowance, expiryTime, description); + } /** diff --git a/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol b/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol index 9c5513ee7..c7c962f4b 100644 --- a/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol +++ b/contracts/modules/TransferManager/ManualApprovalTransferManagerFactory.sol @@ -18,10 +18,10 @@ contract ManualApprovalTransferManagerFactory is ModuleFactory { constructor (address _polyAddress, uint256 _setupCost, uint256 _usageCost, uint256 _subscriptionCost) public ModuleFactory(_polyAddress, _setupCost, _usageCost, _subscriptionCost) { - version = "2.0.1"; + version = "2.1.0"; name = "ManualApprovalTransferManager"; title = "Manual Approval Transfer Manager"; - description = "Manage transfers using single approvals / blocking"; + description = "Manage transfers using single approvals"; compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(0), uint8(0), uint8(0)); } @@ -53,7 +53,7 @@ contract ManualApprovalTransferManagerFactory is ModuleFactory { */ function getInstructions() external view returns(string) { /*solium-disable-next-line max-len*/ - return "Allows an issuer to set manual approvals or blocks for specific pairs of addresses and amounts. Init function takes no parameters."; + return "Allows an issuer to set manual approvals for specific pairs of addresses and amounts. Init function takes no parameters."; } /** diff --git a/contracts/modules/TransferManager/VolumeRestrictionTM.sol b/contracts/modules/TransferManager/VolumeRestrictionTM.sol new file mode 100644 index 000000000..0c6818c03 --- /dev/null +++ b/contracts/modules/TransferManager/VolumeRestrictionTM.sol @@ -0,0 +1,1253 @@ +pragma solidity ^0.4.24; + +import "./ITransferManager.sol"; +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../../libraries/BokkyPooBahsDateTimeLibrary.sol"; +import "../../libraries/VolumeRestrictionLib.sol"; + +contract VolumeRestrictionTM is VolumeRestrictionTMStorage, ITransferManager { + + using SafeMath for uint256; + + // permission definition + bytes32 internal 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, + RestrictionType _typeOfRestriction + ); + // Emit when the new daily (Individual) restriction is added + event AddIndividualDailyRestriction( + address indexed _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _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, + RestrictionType _typeOfRestriction + ); + // Emit when individual daily restriction get modified + event ModifyIndividualDailyRestriction( + address indexed _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _typeOfRestriction + ); + // Emit when the new global restriction is added + event AddDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _typeOfRestriction + ); + // Emit when the new daily (Default) restriction is added + event AddDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _typeOfRestriction + ); + // Emit when default restriction get modified + event ModifyDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _typeOfRestriction + ); + // Emit when daily default restriction get modified + event ModifyDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _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) && exemptIndex[_from] == 0) { + // Function must only be called by the associated security token if _isTransfer == true + require(msg.sender == securityToken || !_isTransfer); + // Checking the individual restriction if the `_from` comes in the individual category + if ((individualRestriction[_from].endTime >= now && individualRestriction[_from].startTime <= now) + || (individualDailyRestriction[_from].endTime >= now && individualDailyRestriction[_from].startTime <= now)) { + + return _restrictionCheck(_isTransfer, _from, _amount, userToBucket[_from], individualRestriction[_from], false); + + // 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 _restrictionCheck(_isTransfer, _from, _amount, defaultUserToBucket[_from], defaultRestriction, true); + } + } + 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) external withPerm(ADMIN) { + require(_wallet != address(0)); + uint256 exemptIndexWallet = exemptIndex[_wallet]; + require((exemptIndexWallet == 0) == _change); + if (_change) { + exemptAddresses.push(_wallet); + exemptIndex[_wallet] = exemptAddresses.length; + } else { + exemptAddresses[exemptIndexWallet - 1] = exemptAddresses[exemptAddresses.length - 1]; + exemptIndex[exemptAddresses[exemptIndexWallet - 1]] = exemptIndexWallet; + delete exemptIndex[_wallet]; + exemptAddresses.length --; + } + 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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function addIndividualRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _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, + RestrictionType _restrictionType + ) + internal + { + // It will help to reduce the chances of transaction failure (Specially when the issuer + // wants to set the startTime near to the current block.timestamp) and minting delayed because + // of the gas fee or network congestion that lead to the process block timestamp may grater + // than the given startTime. + uint256 startTime = _getValidStartTime(_startTime); + require(_holder != address(0) && exemptIndex[_holder] == 0, "Invalid address"); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); + + if (individualRestriction[_holder].endTime != 0) { + _removeIndividualRestriction(_holder); + } + individualRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + VolumeRestrictionLib.addRestrictionData(holderData, _holder, TypeOfPeriod.MultipleDays, individualRestriction[_holder].endTime); + emit AddIndividualRestriction( + _holder, + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _restrictionType + ); + } + + /** + * @notice Use to add the new individual daily 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 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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function addIndividualDailyRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + RestrictionType _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, + RestrictionType _restrictionType + ) + internal + { + uint256 startTime = _getValidStartTime(_startTime); + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, now, false); + if (individualDailyRestriction[_holder].endTime != 0) { + _removeIndividualDailyRestriction(_holder); + } + individualDailyRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + _restrictionType + ); + VolumeRestrictionLib.addRestrictionData(holderData, _holder, TypeOfPeriod.OneDay, individualRestriction[_holder].endTime); + 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 whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function addIndividualDailyRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _endTimes, + RestrictionType[] _restrictionTypes + ) + public //Marked public to save code size + withPerm(ADMIN) + { + //NB - we duplicate _startTimes below to allow function reuse + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); + 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 whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function addIndividualRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + RestrictionType[] _restrictionTypes + ) + public //Marked public to save code size + withPerm(ADMIN) + { + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + 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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function addDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _restrictionType + ) + public //Marked public to save code size + withPerm(ADMIN) + { + uint256 startTime = _getValidStartTime(_startTime); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); + defaultRestriction = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function addDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + RestrictionType _restrictionType + ) + public //Marked public to save code size + withPerm(ADMIN) + { + uint256 startTime = _getValidStartTime(_startTime); + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, now, false); + defaultDailyRestriction = VolumeRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + _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)); + require(individualRestriction[_holder].endTime != 0); + individualRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + VolumeRestrictionLib.deleteHolderFromList(holderData, _holder, TypeOfPeriod.OneDay); + 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)); + require(individualDailyRestriction[_holder].endTime != 0); + individualDailyRestriction[_holder] = VolumeRestriction(0, 0, 0, 0, RestrictionType(0)); + VolumeRestrictionLib.deleteHolderFromList(holderData, _holder, TypeOfPeriod.MultipleDays); + 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() external 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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function modifyIndividualRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _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, + RestrictionType _restrictionType + ) + internal + { + _isAllowedToModify(individualRestriction[_holder].startTime); + uint256 startTime = _getValidStartTime(_startTime); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); + individualRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function modifyIndividualDailyRestriction( + address _holder, + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + RestrictionType _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, + RestrictionType _restrictionType + ) + internal + { + uint256 startTime = _getValidStartTime(_startTime); + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, + (individualDailyRestriction[_holder].startTime <= now ? individualDailyRestriction[_holder].startTime : now), + true + ); + individualDailyRestriction[_holder] = VolumeRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + _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 whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function modifyIndividualDailyRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _endTimes, + RestrictionType[] _restrictionTypes + ) + public //Marked public to save code size + withPerm(ADMIN) + { + //NB - we duplicate _startTimes below to allow function reuse + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _startTimes, _endTimes, _restrictionTypes); + for (uint256 i = 0; i < _holders.length; i++) { + _modifyIndividualDailyRestriction( + _holders[i], + _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 whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function modifyIndividualRestrictionMulti( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + RestrictionType[] _restrictionTypes + ) + public //Marked public to save code size + withPerm(ADMIN) + { + _checkLengthOfArray(_holders, _allowedTokens, _startTimes, _rollingPeriodInDays, _endTimes, _restrictionTypes); + 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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function modifyDefaultRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodInDays, + uint256 _endTime, + RestrictionType _restrictionType + ) + public //Marked public to save code size + withPerm(ADMIN) + { + _isAllowedToModify(defaultRestriction.startTime); + uint256 startTime = _getValidStartTime(_startTime); + _checkInputParams(_allowedTokens, startTime, _rollingPeriodInDays, _endTime, _restrictionType, now, false); + defaultRestriction = VolumeRestriction( + _allowedTokens, + startTime, + _rollingPeriodInDays, + _endTime, + _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 Whether it will be `Fixed` (fixed no. of tokens allowed to transact) + * or `Percentage` (tokens are calculated as per the totalSupply in the fly). + */ + function modifyDefaultDailyRestriction( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _endTime, + RestrictionType _restrictionType + ) + public //Marked public to save code size + withPerm(ADMIN) + { + uint256 startTime = _getValidStartTime(_startTime); + // If old startTime is already passed then new startTime should be greater than or equal to the + // old startTime otherwise any past startTime can be allowed in compare to earlier startTime. + _checkInputParams(_allowedTokens, startTime, 1, _endTime, _restrictionType, + (defaultDailyRestriction.startTime <= now ? defaultDailyRestriction.startTime : now), + true + ); + defaultDailyRestriction = VolumeRestriction( + _allowedTokens, + startTime, + 1, + _endTime, + _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 _restrictionCheck( + bool _isTransfer, + address _from, + uint256 _amount, + BucketDetails memory _bucketDetails, + VolumeRestriction memory _restriction, + bool _isDefault + ) + internal + returns (Result) + { + // using the variable to avoid stack too deep error + VolumeRestriction memory dailyRestriction = _isDefault ? defaultDailyRestriction :individualDailyRestriction[_from]; + uint256 daysCovered = _restriction.rollingPeriodInDays; + uint256 fromTimestamp = 0; + uint256 sumOfLastPeriod = 0; + uint256 dailyTime = 0; + // This variable works for both allowedDefault or allowedIndividual + bool allowedDefault = 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, + _isDefault + ); + // validation of the transaction amount + if ( + !_checkValidAmountToTransact( + _isDefault, _from, sumOfLastPeriod, _amount, _restriction.typeOfRestriction, _restriction.allowedTokens + ) + ) { + allowedDefault = false; + } + } + + (allowedDaily, dailyTime) = _dailyTxCheck(_from, _amount, _bucketDetails.dailyLastTradedDayTime, dailyRestriction, _isDefault); + + if (_isTransfer) { + _updateStorage( + _from, + _amount, + fromTimestamp, + sumOfLastPeriod, + daysCovered, + dailyTime, + _isDefault + ); + } + return ((allowedDaily && allowedDefault) == true ? Result.NA : Result.INVALID); + } + + /** + * @notice The function is used to check specific edge case where the user restriction type change from + * default to individual or vice versa. It will return true when last transaction traded by the user + * and the current txn timestamp lies in the same day. + * NB - Instead of comparing the current day transaction amount, we are comparing the total amount traded + * on the lastTradedDayTime that makes the restriction strict. The reason is not availability of amount + * that transacted on the current day (because of bucket desgin). + */ + function _isValidAmountAfterRestrictionChanges( + bool _isDefault, + address _from, + uint256 _amount, + uint256 _sumOfLastPeriod, + uint256 _allowedAmount + ) + internal + view + returns(bool) + { + // Always use the alternate bucket details as per the current transaction restriction + BucketDetails storage bucketDetails = _isDefault ? userToBucket[_from] : defaultUserToBucket[_from]; + uint256 amountTradedLastDay = _isDefault ? bucket[_from][bucketDetails.lastTradedDayTime]: defaultBucket[_from][bucketDetails.lastTradedDayTime]; + return VolumeRestrictionLib.isValidAmountAfterRestrictionChanges( + amountTradedLastDay, + _amount, + _sumOfLastPeriod, + _allowedAmount, + bucketDetails.lastTradedTimestamp + ); + } + + function _checkValidAmountToTransact( + bool _isDefault, + address _from, + uint256 _sumOfLastPeriod, + uint256 _amountToTransact, + RestrictionType _typeOfRestriction, + uint256 _allowedTokens + ) + internal + view + returns (bool) + { + uint256 allowedAmount; + if (_typeOfRestriction == RestrictionType.Percentage) { + allowedAmount = (_allowedTokens.mul(ISecurityToken(securityToken).totalSupply())) / uint256(10) ** 18; + } else { + allowedAmount = _allowedTokens; + } + // Validation on the amount to transact + bool allowed = allowedAmount >= _sumOfLastPeriod.add(_amountToTransact); + return (allowed && _isValidAmountAfterRestrictionChanges(_isDefault, _from, _amountToTransact, _sumOfLastPeriod, allowedAmount)); + } + + function _dailyTxCheck( + address _from, + uint256 _amount, + uint256 _dailyLastTradedDayTime, + VolumeRestriction memory _restriction, + bool _isDefault + ) + 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; + // This if condition will be executed when the individual daily restriction executed first time + if (_dailyLastTradedDayTime == 0 || _dailyLastTradedDayTime < _restriction.startTime) + _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 + if (_isDefault) + txSumOfDay = defaultBucket[_from][_dailyLastTradedDayTime]; + else + txSumOfDay = bucket[_from][_dailyLastTradedDayTime]; + return ( + _checkValidAmountToTransact( + _isDefault, + _from, + txSumOfDay, + _amount, + _restriction.typeOfRestriction, + _restriction.allowedTokens + ), + _dailyLastTradedDayTime + ); + } + return (true, _dailyLastTradedDayTime); + } + + /// Internal function for the bucket check + function _bucketCheck( + uint256 _fromTime, + uint256 _diffDays, + address _from, + uint256 _rollingPeriodInDays, + BucketDetails memory _bucketDetails, + bool isDefault + ) + 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. + uint256 temp = _bucketDetails.daysCovered.sub(counter.sub(_rollingPeriodInDays)); + temp = _bucketDetails.lastTradedDayTime.sub(temp.mul(1 days)); + if (isDefault) + sumOfLastPeriod = sumOfLastPeriod.sub(defaultBucket[_from][temp]); + else + sumOfLastPeriod = sumOfLastPeriod.sub(bucket[_from][temp]); + } + // 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 _updateStorage( + address _from, + uint256 _amount, + uint256 _lastTradedDayTime, + uint256 _sumOfLastPeriod, + uint256 _daysCovered, + uint256 _dailyLastTradedDayTime, + bool isDefault + ) + internal + { + + if (isDefault){ + BucketDetails storage defaultUserToBucketDetails = defaultUserToBucket[_from]; + _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, defaultDailyRestriction.endTime, true, defaultUserToBucketDetails); + } + else { + BucketDetails storage userToBucketDetails = userToBucket[_from]; + uint256 _endTime = individualDailyRestriction[_from].endTime; + _updateStorageActual(_from, _amount, _lastTradedDayTime, _sumOfLastPeriod, _daysCovered, _dailyLastTradedDayTime, _endTime, false, userToBucketDetails); + } + } + + function _updateStorageActual( + address _from, + uint256 _amount, + uint256 _lastTradedDayTime, + uint256 _sumOfLastPeriod, + uint256 _daysCovered, + uint256 _dailyLastTradedDayTime, + uint256 _endTime, + bool isDefault, + 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; + } + // Assigning the latest transaction timestamp + details.lastTradedTimestamp = now; + if (_amount != 0) { + if (_lastTradedDayTime !=0) { + details.sumOfLastPeriod = _sumOfLastPeriod.add(_amount); + // Increasing the total amount of the day by `_amount` + if (isDefault) + defaultBucket[_from][_lastTradedDayTime] = defaultBucket[_from][_lastTradedDayTime].add(_amount); + else + bucket[_from][_lastTradedDayTime] = bucket[_from][_lastTradedDayTime].add(_amount); + } + if ((_dailyLastTradedDayTime != _lastTradedDayTime) && _dailyLastTradedDayTime != 0 && now <= _endTime) { + // Increasing the total amount of the day by `_amount` + if (isDefault) + defaultBucket[_from][_dailyLastTradedDayTime] = defaultBucket[_from][_dailyLastTradedDayTime].add(_amount); + else + bucket[_from][_dailyLastTradedDayTime] = bucket[_from][_dailyLastTradedDayTime].add(_amount); + + } + } + } + + function _checkInputParams( + uint256 _allowedTokens, + uint256 _startTime, + uint256 _rollingPeriodDays, + uint256 _endTime, + RestrictionType _restrictionType, + uint256 _earliestStartTime, + bool isModifyDaily + ) + internal + pure + { + if (isModifyDaily) + require(_startTime >= _earliestStartTime, "Invalid startTime"); + else + require(_startTime > _earliestStartTime, "Invalid startTime"); + require(_allowedTokens > 0); + if (_restrictionType != RestrictionType.Fixed) { + require(_allowedTokens <= 100 * 10 ** 16, "Invalid value"); + } + // Maximum limit for the rollingPeriod is 365 days + require(_rollingPeriodDays >= 1 && _rollingPeriodDays <= 365, "Invalid rollingperiod"); + require( + BokkyPooBahsDateTimeLibrary.diffDays(_startTime, _endTime) >= _rollingPeriodDays, + "Invalid times" + ); + } + + function _isAllowedToModify(uint256 _startTime) internal view { + require(_startTime > now); + } + + function _getValidStartTime(uint256 _startTime) internal view returns(uint256) { + if (_startTime == 0) + _startTime = now + 1; + return _startTime; + } + + /** + * @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, uint256) { + return _getBucketDetails(userToBucket[_user]); + } + + /** + * @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, uint256) { + return _getBucketDetails(defaultUserToBucket[_user]); + } + + function _getBucketDetails(BucketDetails storage _bucket) internal view returns( + uint256, + uint256, + uint256, + uint256, + uint256 + ) { + return( + _bucket.lastTradedDayTime, + _bucket.sumOfLastPeriod, + _bucket.daysCovered, + _bucket.dailyLastTradedDayTime, + _bucket.lastTradedTimestamp + ); + } + + /** + * @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].add(defaultBucket[_user][_at])); + } + + /** + * @notice This function returns the signature of configure function + */ + function getInitFunction() public view returns(bytes4) { + return bytes4(0); + } + + /** + * @notice Use to return the list of exempted addresses + */ + function getExemptAddress() external view returns(address[]) { + return exemptAddresses; + } + + /** + * @notice Provide the restriction details of all the restricted addresses + * @return address List of the restricted addresses + * @return uint256 List of the tokens allowed to the restricted addresses corresponds to restricted address + * @return uint256 List of the start time of the restriction corresponds to restricted address + * @return uint256 List of the rolling period in days for a restriction corresponds to restricted address. + * @return uint256 List of the end time of the restriction corresponds to restricted address. + * @return RestrictionType List of the type of restriction to validate the value of the `allowedTokens` + * of the restriction corresponds to restricted address + */ + function getRestrictedData() external view returns( + address[] memory allAddresses, + uint256[] memory allowedTokens, + uint256[] memory startTime, + uint256[] memory rollingPeriodInDays, + uint256[] memory endTime, + RestrictionType[] memory typeOfRestriction + ) { + uint256 counter = 0; + uint256 i = 0; + for (i = 0; i < holderData.restrictedAddresses.length; i++) { + counter = counter + uint256( + holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.Both ? TypeOfPeriod.Both : TypeOfPeriod.OneDay + ); + } + allAddresses = new address[](counter); + allowedTokens = new uint256[](counter); + startTime = new uint256[](counter); + rollingPeriodInDays = new uint256[](counter); + endTime = new uint256[](counter); + typeOfRestriction = new RestrictionType[](counter); + counter = 0; + for (i = 0; i < holderData.restrictedAddresses.length; i++) { + allAddresses[counter] = holderData.restrictedAddresses[i]; + if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.MultipleDays) { + _setValues(individualRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + else if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.OneDay) { + _setValues(individualDailyRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + else if (holderData.restrictedHolders[holderData.restrictedAddresses[i]].typeOfPeriod == TypeOfPeriod.Both) { + _setValues(individualRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + counter = counter + 1; + allAddresses[counter] = holderData.restrictedAddresses[i]; + _setValues(individualDailyRestriction[holderData.restrictedAddresses[i]], allowedTokens, startTime, rollingPeriodInDays, endTime, typeOfRestriction, counter); + } + counter ++; + } + } + + function _setValues( + VolumeRestriction memory _restriction, + uint256[] memory _allowedTokens, + uint256[] memory _startTime, + uint256[] memory _rollingPeriodInDays, + uint256[] memory _endTime, + RestrictionType[] memory _typeOfRestriction, + uint256 _index + ) + internal + pure + { + _allowedTokens[_index] = _restriction.allowedTokens; + _startTime[_index] = _restriction.startTime; + _rollingPeriodInDays[_index] = _restriction.rollingPeriodInDays; + _endTime[_index] = _restriction.endTime; + _typeOfRestriction[_index] = _restriction.typeOfRestriction; + } + + function _checkLengthOfArray( + address[] _holders, + uint256[] _allowedTokens, + uint256[] _startTimes, + uint256[] _rollingPeriodInDays, + uint256[] _endTimes, + VolumeRestrictionTMStorage.RestrictionType[] _restrictionTypes + ) + internal + pure + { + require( + _holders.length == _allowedTokens.length && + _allowedTokens.length == _startTimes.length && + _startTimes.length == _rollingPeriodInDays.length && + _rollingPeriodInDays.length == _endTimes.length && + _endTimes.length == _restrictionTypes.length, + "Length mismatch" + ); + } + + /** + * @notice Returns the permissions flag that are associated with Percentage transfer Manager + */ + function getPermissions() public view returns(bytes32[] memory allPermissions) { + allPermissions = new bytes32[](1); + allPermissions[0] = ADMIN; + } + +} 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/proxy/ERC20DividendCheckpointProxy.sol b/contracts/proxy/ERC20DividendCheckpointProxy.sol new file mode 100644 index 000000000..8839d30e1 --- /dev/null +++ b/contracts/proxy/ERC20DividendCheckpointProxy.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.4.24; + +import "../modules/Checkpoint/ERC20DividendCheckpointStorage.sol"; +import "../modules/Checkpoint/DividendCheckpointStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "../modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract ERC20DividendCheckpointProxy is ERC20DividendCheckpointStorage, DividendCheckpointStorage, 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/contracts/proxy/EtherDividendCheckpointProxy.sol b/contracts/proxy/EtherDividendCheckpointProxy.sol new file mode 100644 index 000000000..40b1c1332 --- /dev/null +++ b/contracts/proxy/EtherDividendCheckpointProxy.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.24; + +import "../modules/Checkpoint/DividendCheckpointStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "../modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract EtherDividendCheckpointProxy is DividendCheckpointStorage, 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/contracts/proxy/GeneralTransferManagerProxy.sol b/contracts/proxy/GeneralTransferManagerProxy.sol new file mode 100644 index 000000000..0fbaa7880 --- /dev/null +++ b/contracts/proxy/GeneralTransferManagerProxy.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.24; + +import "../storage/GeneralTransferManagerStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "../modules/ModuleStorage.sol"; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract GeneralTransferManagerProxy is GeneralTransferManagerStorage, 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/contracts/proxy/OwnedProxy.sol b/contracts/proxy/OwnedProxy.sol new file mode 100644 index 000000000..b75142fbe --- /dev/null +++ b/contracts/proxy/OwnedProxy.sol @@ -0,0 +1,91 @@ +pragma solidity ^0.4.18; + +import "./Proxy.sol"; + +/** + * @title OwnedProxy + * @dev This contract combines an upgradeability proxy with basic authorization control functionalities + */ +contract OwnedProxy is Proxy { + + // Owner of the contract + address private __owner; + + // Address of the current implementation + address internal __implementation; + + /** + * @dev Event to show ownership has been transferred + * @param _previousOwner representing the address of the previous owner + * @param _newOwner representing the address of the new owner + */ + event ProxyOwnershipTransferred(address _previousOwner, address _newOwner); + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier ifOwner() { + if (msg.sender == _owner()) { + _; + } else { + _fallback(); + } + } + + /** + * @dev the constructor sets the original owner of the contract to the sender account. + */ + constructor() public { + _setOwner(msg.sender); + } + + /** + * @dev Tells the address of the owner + * @return the address of the owner + */ + function _owner() internal view returns (address) { + return __owner; + } + + /** + * @dev Sets the address of the owner + */ + function _setOwner(address _newOwner) internal { + require(_newOwner != address(0), "Address should not be 0x"); + __owner = _newOwner; + } + + /** + * @notice Internal function to provide the address of the implementation contract + */ + function _implementation() internal view returns (address) { + return __implementation; + } + + /** + * @dev Tells the address of the proxy owner + * @return the address of the proxy owner + */ + function proxyOwner() external ifOwner returns (address) { + return _owner(); + } + + /** + * @dev Tells the address of the current implementation + * @return address of the current implementation + */ + function implementation() external ifOwner returns (address) { + return _implementation(); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function transferProxyOwnership(address _newOwner) external ifOwner { + require(_newOwner != address(0), "Address should not be 0x"); + emit ProxyOwnershipTransferred(_owner(), _newOwner); + _setOwner(_newOwner); + } + +} diff --git a/contracts/proxy/USDTieredSTOProxy.sol b/contracts/proxy/USDTieredSTOProxy.sol new file mode 100644 index 000000000..050e14a21 --- /dev/null +++ b/contracts/proxy/USDTieredSTOProxy.sol @@ -0,0 +1,32 @@ +pragma solidity ^0.4.24; + +import "../storage/USDTieredSTOStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "openzeppelin-solidity/contracts/ReentrancyGuard.sol"; +import "../modules/STO/STOStorage.sol"; +import "../modules/ModuleStorage.sol"; + +/** + * @title USDTiered STO module Proxy + */ +contract USDTieredSTOProxy is USDTieredSTOStorage, STOStorage, ModuleStorage, Pausable, ReentrancyGuard, OwnedProxy { + + /** + * @notice Constructor + * @param _securityToken Address of the security token + * @param _polyAddress Address of the polytoken + * @param _implementation representing the address of the new implementation to be set + */ + constructor (address _securityToken, address _polyAddress, address _implementation) + public + ModuleStorage(_securityToken, _polyAddress) + { + require( + _implementation != address(0), + "Implementation address should not be 0x" + ); + __implementation = _implementation; + } + +} diff --git a/contracts/proxy/VestingEscrowWalletProxy.sol b/contracts/proxy/VestingEscrowWalletProxy.sol new file mode 100644 index 000000000..8f7be97ce --- /dev/null +++ b/contracts/proxy/VestingEscrowWalletProxy.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.4.24; + +import "../storage/VestingEscrowWalletStorage.sol"; +import "./OwnedProxy.sol"; +import "../Pausable.sol"; +import "../modules/ModuleStorage.sol"; + /** + * @title Escrow wallet module for vesting functionality + */ +contract VestingEscrowWalletProxy is VestingEscrowWalletStorage, 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; + } + } \ No newline at end of file diff --git a/contracts/proxy/VolumeRestrictionTMProxy.sol b/contracts/proxy/VolumeRestrictionTMProxy.sol new file mode 100644 index 000000000..0f5cc7f5a --- /dev/null +++ b/contracts/proxy/VolumeRestrictionTMProxy.sol @@ -0,0 +1,30 @@ +pragma solidity ^0.4.24; + +import "../storage/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/contracts/storage/EternalStorage.sol b/contracts/storage/EternalStorage.sol index 637b5f2f3..a3f76203b 100644 --- a/contracts/storage/EternalStorage.sol +++ b/contracts/storage/EternalStorage.sol @@ -57,39 +57,6 @@ contract EternalStorage { stringStorage[_key] = _value; } - //////////////////// - /// get functions - //////////////////// - /// @notice Get function use to get the value of the singleton state variables - /// Ex1- string public version = "0.0.1"; - /// string _version = getString(keccak256(abi.encodePacked("version")); - /// Ex2 - assert(temp1 == temp2); replace to - /// assert(getUint(keccak256(abi.encodePacked(temp1)) == getUint(keccak256(abi.encodePacked(temp2)); - /// Ex3 - mapping(string => SymbolDetails) registeredSymbols; where SymbolDetails is the structure having different type of values as - /// {uint256 date, string name, address owner} etc. - /// string _name = getString(keccak256(abi.encodePacked("registeredSymbols_name", "TOKEN")); - - function getBool(bytes32 _key) internal view returns (bool) { - return boolStorage[_key]; - } - - function getUint(bytes32 _key) internal view returns (uint256) { - return uintStorage[_key]; - } - - function getAddress(bytes32 _key) internal view returns (address) { - return addressStorage[_key]; - } - - function getString(bytes32 _key) internal view returns (string) { - return stringStorage[_key]; - } - - function getBytes32(bytes32 _key) internal view returns (bytes32) { - return bytes32Storage[_key]; - } - - //////////////////////////// // deleteArray functions //////////////////////////// @@ -192,19 +159,15 @@ contract EternalStorage { /// Ex2- uint256 _len = tokensOwnedByOwner[0x1].length; replace with /// getArrayBytes32(keccak256(abi.encodePacked("tokensOwnedByOwner", 0x1)).length; - function getArrayAddress(bytes32 _key) internal view returns(address[]) { + function getArrayAddress(bytes32 _key) public view returns(address[]) { return addressArrayStorage[_key]; } - function getArrayBytes32(bytes32 _key) internal view returns(bytes32[]) { + function getArrayBytes32(bytes32 _key) public view returns(bytes32[]) { return bytes32ArrayStorage[_key]; } - function getArrayString(bytes32 _key) internal view returns(string[]) { - return stringArrayStorage[_key]; - } - - function getArrayUint(bytes32 _key) internal view returns(uint[]) { + function getArrayUint(bytes32 _key) public view returns(uint[]) { return uintArrayStorage[_key]; } @@ -232,31 +195,38 @@ contract EternalStorage { stringArrayStorage[_key][_index] = _value; } - ///////////////////////////// - /// Public getters functions - ///////////////////////////// + /// Public getters functions + //////////////////// + /// @notice Get function use to get the value of the singleton state variables + /// Ex1- string public version = "0.0.1"; + /// string _version = getString(keccak256(abi.encodePacked("version")); + /// Ex2 - assert(temp1 == temp2); replace to + /// assert(getUint(keccak256(abi.encodePacked(temp1)) == getUint(keccak256(abi.encodePacked(temp2)); + /// Ex3 - mapping(string => SymbolDetails) registeredSymbols; where SymbolDetails is the structure having different type of values as + /// {uint256 date, string name, address owner} etc. + /// string _name = getString(keccak256(abi.encodePacked("registeredSymbols_name", "TOKEN")); - function getUintValues(bytes32 _variable) public view returns(uint256) { + function getUintValue(bytes32 _variable) public view returns(uint256) { return uintStorage[_variable]; } - function getBoolValues(bytes32 _variable) public view returns(bool) { + function getBoolValue(bytes32 _variable) public view returns(bool) { return boolStorage[_variable]; } - function getStringValues(bytes32 _variable) public view returns(string) { + function getStringValue(bytes32 _variable) public view returns(string) { return stringStorage[_variable]; } - function getAddressValues(bytes32 _variable) public view returns(address) { + function getAddressValue(bytes32 _variable) public view returns(address) { return addressStorage[_variable]; } - function getBytes32Values(bytes32 _variable) public view returns(bytes32) { + function getBytes32Value(bytes32 _variable) public view returns(bytes32) { return bytes32Storage[_variable]; } - function getBytesValues(bytes32 _variable) public view returns(bytes) { + function getBytesValue(bytes32 _variable) public view returns(bytes) { return bytesStorage[_variable]; } diff --git a/contracts/storage/GeneralTransferManagerStorage.sol b/contracts/storage/GeneralTransferManagerStorage.sol new file mode 100644 index 000000000..87f886ae7 --- /dev/null +++ b/contracts/storage/GeneralTransferManagerStorage.sol @@ -0,0 +1,55 @@ +pragma solidity ^0.4.24; + +/** + * @title Transfer Manager module for core transfer validation functionality + */ +contract GeneralTransferManagerStorage { + + //Address from which issuances come + address public issuanceAddress = address(0); + + //Address which can sign whitelist changes + address public signingAddress = address(0); + + bytes32 public constant WHITELIST = "WHITELIST"; + bytes32 public constant FLAGS = "FLAGS"; + + //from and to timestamps that an investor can send / receive tokens respectively + struct TimeRestriction { + //the moment when the sale lockup period ends and the investor can freely sell or transfer away their tokens + uint64 canSendAfter; + //the moment when the purchase lockup period ends and the investor can freely purchase or receive from others + uint64 canReceiveAfter; + uint64 expiryTime; + uint8 canBuyFromSTO; + uint8 added; + } + + // Allows all TimeRestrictions to be offset + struct Defaults { + uint64 canSendAfter; + uint64 canReceiveAfter; + } + + // Offset to be applied to all timings (except KYC expiry) + Defaults public defaults; + + // List of all addresses that have been added to the GTM at some point + address[] public investors; + + // An address can only send / receive tokens once their corresponding uint256 > block.number + // (unless allowAllTransfers == true or allowAllWhitelistTransfers == true) + mapping (address => TimeRestriction) public whitelist; + // Map of used nonces by customer + mapping(address => mapping(uint256 => bool)) public nonceMap; + + //If true, there are no transfer restrictions, for any addresses + bool public allowAllTransfers = false; + //If true, time lock is ignored for transfers (address must still be on whitelist) + bool public allowAllWhitelistTransfers = false; + //If true, time lock is ignored for issuances (address must still be on whitelist) + bool public allowAllWhitelistIssuances = true; + //If true, time lock is ignored for burn transactions + bool public allowAllBurnTransfers = false; + +} diff --git a/contracts/storage/USDTieredSTOStorage.sol b/contracts/storage/USDTieredSTOStorage.sol new file mode 100644 index 000000000..31e9d803d --- /dev/null +++ b/contracts/storage/USDTieredSTOStorage.sol @@ -0,0 +1,97 @@ +pragma solidity ^0.4.24; + +import "../interfaces/IERC20.sol"; + +/** + * @title Contract used to store layout for the USDTieredSTO storage + */ +contract USDTieredSTOStorage { + + ///////////// + // Storage // + ///////////// + struct Tier { + // NB rates mentioned below are actually price and are used like price in the logic. + // How many token units a buyer gets per USD in this tier (multiplied by 10**18) + uint256 rate; + + // How many token units a buyer gets per USD in this tier (multiplied by 10**18) when investing in POLY up to tokensDiscountPoly + uint256 rateDiscountPoly; + + // How many tokens are available in this tier (relative to totalSupply) + uint256 tokenTotal; + + // How many token units are available in this tier (relative to totalSupply) at the ratePerTierDiscountPoly rate + uint256 tokensDiscountPoly; + + // How many tokens have been minted in this tier (relative to totalSupply) + uint256 mintedTotal; + + // How many tokens have been minted in this tier (relative to totalSupply) for each fund raise type + mapping (uint8 => uint256) minted; + + // How many tokens have been minted in this tier (relative to totalSupply) at discounted POLY rate + uint256 mintedDiscountPoly; + } + + struct Investor { + // Whether investor is accredited (0 = non-accredited, 1 = accredited) + uint8 accredited; + // Whether we have seen the investor before (already added to investors list) + uint8 seen; + // Overrides for default limit in USD for non-accredited investors multiplied by 10**18 (0 = no override) + uint256 nonAccreditedLimitUSDOverride; + } + + mapping (bytes32 => mapping (bytes32 => string)) oracleKeys; + + // Determine whether users can invest on behalf of a beneficiary + bool public allowBeneficialInvestments = false; + + // Whether or not the STO has been finalized + bool public isFinalized; + + // Address of issuer reserve wallet for unsold tokens + address public reserveWallet; + + // List of stable coin addresses + address[] public usdTokens; + + // Current tier + uint256 public currentTier; + + // Amount of USD funds raised + uint256 public fundsRaisedUSD; + + // Amount of stable coins raised + mapping (address => uint256) public stableCoinsRaised; + + // Amount in USD invested by each address + mapping (address => uint256) public investorInvestedUSD; + + // Amount in fund raise type invested by each investor + mapping (address => mapping (uint8 => uint256)) public investorInvested; + + // Accredited & non-accredited investor data + mapping (address => Investor) public investors; + + // List of active stable coin addresses + mapping (address => bool) public usdTokenEnabled; + + // List of all addresses that have been added as accredited or non-accredited without + // the default limit + address[] public investorsList; + + // Default limit in USD for non-accredited investors multiplied by 10**18 + uint256 public nonAccreditedLimitUSD; + + // Minimum investable amount in USD + uint256 public minimumInvestmentUSD; + + // Final amount of tokens returned to issuer + uint256 public finalAmountReturned; + + // Array of Tiers + Tier[] public tiers; + +} diff --git a/contracts/storage/VestingEscrowWalletStorage.sol b/contracts/storage/VestingEscrowWalletStorage.sol new file mode 100644 index 000000000..af40d32bf --- /dev/null +++ b/contracts/storage/VestingEscrowWalletStorage.sol @@ -0,0 +1,54 @@ +pragma solidity ^0.4.24; + +/** + * @title Wallet for core vesting escrow functionality + */ +contract VestingEscrowWalletStorage { + + struct Schedule { + // Name of the template + bytes32 templateName; + // Tokens that were already claimed + uint256 claimedTokens; + // Start time of the schedule + uint256 startTime; + } + + struct Template { + // Total amount of tokens + uint256 numberOfTokens; + // Schedule duration (How long the schedule will last) + uint256 duration; + // Schedule frequency (It is a cliff time period) + uint256 frequency; + // Index of the template in an array template names + uint256 index; + } + + // Number of tokens that are hold by the `this` contract but are unassigned to any schedule + uint256 public unassignedTokens; + // Address of the Treasury wallet. All of the unassigned token will transfer to that address. + address public treasuryWallet; + // List of all beneficiaries who have the schedules running/completed/created + address[] public beneficiaries; + // Flag whether beneficiary has been already added or not + mapping(address => bool) internal beneficiaryAdded; + + // Holds schedules array corresponds to the affiliate/employee address + mapping(address => Schedule[]) public schedules; + // Holds template names array corresponds to the affiliate/employee address + mapping(address => bytes32[]) internal userToTemplates; + // Mapping use to store the indexes for different template names for a user. + // affiliate/employee address => template name => index + mapping(address => mapping(bytes32 => uint256)) internal userToTemplateIndex; + // Holds affiliate/employee addresses coressponds to the template name + mapping(bytes32 => address[]) internal templateToUsers; + // Mapping use to store the indexes for different users for a template. + // template name => affiliate/employee address => index + mapping(bytes32 => mapping(address => uint256)) internal templateToUserIndex; + // Store the template details corresponds to the template name + mapping(bytes32 => Template) templates; + + // List of all template names + bytes32[] public templateNames; +} \ No newline at end of file diff --git a/contracts/storage/VolumeRestrictionTMStorage.sol b/contracts/storage/VolumeRestrictionTMStorage.sol new file mode 100644 index 000000000..bf6555ac9 --- /dev/null +++ b/contracts/storage/VolumeRestrictionTMStorage.sol @@ -0,0 +1,68 @@ +pragma solidity ^0.4.24; + +/** + * @title Storage layout for VolumeRestrictionTM + */ +contract VolumeRestrictionTMStorage { + + enum RestrictionType { Fixed, Percentage } + + enum TypeOfPeriod { MultipleDays, OneDay, Both } + + struct RestrictedHolder { + // 1 represent true & 0 for false + uint8 seen; + // Type of period will be enum index of TypeOfPeriod enum + TypeOfPeriod typeOfPeriod; + // Index of the array where the holder address lives + uint128 index; + } + + struct RestrictedData { + mapping(address => RestrictedHolder) restrictedHolders; + address[] restrictedAddresses; + } + + 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; + uint256 lastTradedTimestamp; // It is the timestamp at which last transaction get executed + uint256 lastTradedAmount; // It is the timestamp at which last transaction get executed + } + + // 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 _from => day's timestamp => total amount transact in a day --default + mapping(address => mapping(uint256 => uint256)) internal defaultBucket; + // 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; + // Restricted data (refernce from the VolumeRestrictionLib library ) + RestrictedData holderData; + // Hold exempt index + mapping(address => uint256) exemptIndex; + address[] public exemptAddresses; + +} diff --git a/contracts/tokens/SecurityToken.sol b/contracts/tokens/SecurityToken.sol index e9782b2d1..d2aaa9b55 100644 --- a/contracts/tokens/SecurityToken.sol +++ b/contracts/tokens/SecurityToken.sol @@ -499,14 +499,14 @@ contract SecurityToken is StandardToken, DetailedERC20, ReentrancyGuard, Registr } /** - * @notice Internal - adjusts totalSupply at checkpoint after minting or burning tokens + * @notice Internal - adjusts totalSupply at checkpoint before minting or burning tokens */ function _adjustTotalSupplyCheckpoints() internal { TokenLib.adjustCheckpoints(checkpointTotalSupply, totalSupply(), currentCheckpointId); } /** - * @notice Internal - adjusts token holder balance at checkpoint after a token transfer + * @notice Internal - adjusts token holder balance at checkpoint before a token transfer * @param _investor address of the token holder affected */ function _adjustBalanceCheckpoints(address _investor) internal { diff --git a/docs/permissions_list.md b/docs/permissions_list.md index b49c95a0e..9e7a213db 100644 --- a/docs/permissions_list.md +++ b/docs/permissions_list.md @@ -143,7 +143,7 @@ allocateTokensMulti() - TransferManager + TransferManager CountTransferManager changeHolderCount() withPerm(ADMIN) @@ -176,19 +176,13 @@ modifyWhitelistMulti() - ManualApprovalTransferManager + ManualApprovalTransferManager addManualApproval() - withPerm(TRANSFER_APPROVAL) - - - addManualBlocking() + withPerm(TRANSFER_APPROVAL) revokeManualApproval() - - revokeManualBlocking() - PercentageTransferManager modifyWhitelist() @@ -205,72 +199,142 @@ changeHolderPercentage() - LockupVolumeRestrictionTM - addLockup() - withPerm(ADMIN) + VolumeRestrictionTM + changeExemptWalletList() + withPerm(ADMIN) - addLockUpMulti() + addIndividualRestriction() - removeLockUp() + addIndividualRestrictionMulti() - modifyLockUp() + addGlobalRestriction() - SingleTradeVolumeRestrictionTM - setAllowPrimaryIssuance() - withPerm(ADMIN) + addDailyGlobalRestriction() + + + removeIndividualRestriction() + + + removeIndividualRestrictionMulti() + + + removeGlobalRestriction() + + + removeDailyGlobalRestriction() + + + modifyIndividualRestriction() + + + modifyIndividualRestrictionMulti() + + + modifyGlobalRestriction() + + + modifyDailyGlobalRestriction() + + + BlacklistTransferManager + addBlacklistType() + withPerm(ADMIN) + + + addBlacklistTypeMulti() - changeTransferLimitToPercentage() + modifyBlacklistType() - changeTransferLimitToTokens() + modifyBlacklistTypeMulti() - changeGlobalLimitInTokens() + deleteBlacklistType() - changeGlobalLimitInPercentage() + deleteBlacklistTypeMulti() - addExemptWallet() + addInvestorToBlacklist() - removeExemptWallet() + addInvestorToBlacklistMulti() - addExemptWalletMulti() + addMultiInvestorToBlacklistMulti() - removeExemptWalletMulti() + addInvestorToNewBlacklist() - setTransferLimitInTokens() + deleteInvestorFromAllBlacklist() - setTransferLimitInPercentage() + deleteInvestorFromAllBlacklistMulti() - removeTransferLimitInPercentage() + deleteInvestorFromBlacklist() - removeTransferLimitInTokens() + deleteMultiInvestorsFromBlacklistMulti() - setTransferLimitInTokensMulti() + Wallet + VestingEscrowWallet + changeTreasuryWallet() + onlyOwner - setTransferLimitInPercentageMulti() + depositTokens() + withPerm(ADMIN) - removeTransferLimitInTokensMulti() + sendToTreasury() - removeTransferLimitInPercentageMulti + pushAvailableTokens() + + addTemplate() + + + removeTemplate() + + + addSchedule() + + + addScheduleFromTemplate() + + + modifySchedule() + + + revokeSchedule() + + + revokeAllSchedules() + + + pushAvailableTokensMulti() + + + addScheduleMulti() + + + addScheduleFromTemplateMulti() + + + revokeSchedulesMulti() + + + modifyScheduleMulti() + diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index 14bdf9fed..ff27206a9 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,11 +1,18 @@ const PolymathRegistry = artifacts.require('./PolymathRegistry.sol') const GeneralTransferManagerFactory = artifacts.require('./GeneralTransferManagerFactory.sol') +const GeneralTransferManagerLogic = artifacts.require('./GeneralTransferManager.sol') const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol') const PercentageTransferManagerFactory = artifacts.require('./PercentageTransferManagerFactory.sol') -const USDTieredSTOProxyFactory = artifacts.require('./USDTieredSTOProxyFactory.sol'); +const USDTieredSTOLogic = artifacts.require('./USDTieredSTO.sol'); const CountTransferManagerFactory = artifacts.require('./CountTransferManagerFactory.sol') +const EtherDividendCheckpointLogic = artifacts.require('./EtherDividendCheckpoint.sol') +const ERC20DividendCheckpointLogic = artifacts.require('./ERC20DividendCheckpoint.sol') const EtherDividendCheckpointFactory = artifacts.require('./EtherDividendCheckpointFactory.sol') const ERC20DividendCheckpointFactory = artifacts.require('./ERC20DividendCheckpointFactory.sol') +const LockUpTransferManagerFactory = artifacts.require('./LockUpTransferManagerFactory.sol') +const BlacklistTransferManagerFactory = artifacts.require('./BlacklistTransferManagerFactory.sol') +const VestingEscrowWalletFactory = artifacts.require('./VestingEscrowWalletFactory.sol'); +const VestingEscrowWalletLogic = artifacts.require('./VestingEscrowWallet.sol'); const ModuleRegistry = artifacts.require('./ModuleRegistry.sol'); const ModuleRegistryProxy = artifacts.require('./ModuleRegistryProxy.sol'); const ManualApprovalTransferManagerFactory = artifacts.require('./ManualApprovalTransferManagerFactory.sol') @@ -13,11 +20,14 @@ 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') const MockOracle = artifacts.require('./MockOracle.sol') const TokenLib = artifacts.require('./TokenLib.sol'); +const VolumeRestrictionLib = artifacts.require('./VolumeRestrictionLib.sol'); const SecurityToken = artifacts.require('./tokens/SecurityToken.sol') let BigNumber = require('bignumber.js'); @@ -41,17 +51,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; }); @@ -73,12 +83,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; }); @@ -89,23 +99,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' }] }; @@ -113,186 +123,263 @@ 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(() => { + return deployer.deploy(VolumeRestrictionLib, { from: PolymathAccount }); + }).then(() => { + // Link libraries + deployer.link(VolumeRestrictionLib, VolumeRestrictionTMLogic); deployer.link(TokenLib, SecurityToken); deployer.link(TokenLib, STFactory); // A) Deploy the ModuleRegistry Contract (It contains the list of verified ModuleFactory) - 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 }); + }).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 }); + }).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 }); + }).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 }); + }).then(() => { + // B) Deploy the USDTieredSTOLogic Contract (Factory used to generate the USDTieredSTO contract and this + // manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(USDTieredSTOLogic, "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: PolymathAccount }); + }).then(() => { + // B) Deploy the 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 }); }).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, {from: PolymathAccount}); + return deployer.deploy(GeneralTransferManagerFactory, PolyToken, 0, 0, 0, GeneralTransferManagerLogic.address, { from: PolymathAccount }); + }).then(() => { + // C) Deploy the LockUpTransferManagerFactory Contract (Factory used to generate the LockUpTransferManager contract and + // this manager attach with the securityToken contract at the time of deployment) + return deployer.deploy(LockUpTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).then(() => { // C) Deploy the GeneralPermissionManagerFactory Contract (Factory used to generate the GeneralPermissionManager contract and // this manager attach with the securityToken contract at the time of deployment) - 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 BlacklistTransferManagerFactory Contract (Factory used to generate the BlacklistTransferManager contract use + // to to automate blacklist and restrict transfers) + return deployer.deploy(BlacklistTransferManagerFactory, PolyToken, 0, 0, 0, { from: PolymathAccount }); }).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, {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, {from: PolymathAccount}); + return deployer.deploy(ERC20DividendCheckpointFactory, PolyToken, 0, 0, 0, ERC20DividendCheckpointLogic.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 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 }); }).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 LockUpTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the LockUpTransferManager contract. + return moduleRegistry.registerModule(LockUpTransferManagerFactory.address, { from: PolymathAccount }); }).then(() => { // D) Register the PercentageTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the PercentageTransferManager contract. - 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 }); }).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(() => { + // D) Register the BlacklistTransferManagerFactory in the ModuleRegistry to make the factory available at the protocol level. + // So any securityToken can use that factory to generate the GeneralTransferManager contract. + return moduleRegistry.registerModule(BlacklistTransferManagerFactory.address, { from: PolymathAccount }); }).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 BlacklistTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(BlacklistTransferManagerFactory.address, true, { from: PolymathAccount }); }).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(() => { + // F) Once the LockUpTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken + // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. + // Here it gets verified because it is deployed by the third party account (Polymath Account) not with the issuer accounts. + return moduleRegistry.verifyModule(LockUpTransferManagerFactory.address, true, { from: PolymathAccount }); }).then(() => { // G) Once the PercentageTransferManagerFactory registered with the ModuleRegistry contract then for making them accessble to the securityToken // contract, Factory should comes under the verified list of factories or those factories deployed by the securityToken issuers only. // 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 }); }).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}) - }).then(() => { - // Deploy the proxy factory - return deployer.deploy(USDTieredSTOProxyFactory, {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, USDTieredSTOProxyFactory.address, {from: PolymathAccount}) + return deployer.deploy(USDTieredSTOFactory, PolyToken, usdTieredSTOSetupCost, 0, 0, USDTieredSTOLogic.address, { from: PolymathAccount }) }).then(() => { // I) Register the USDTieredSTOFactory in the ModuleRegistry to make the factory available at the protocol level. // So any securityToken can use that factory to generate the USDTieredSTOFactory contract. - 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(` @@ -306,18 +393,27 @@ module.exports = function (deployer, network, accounts) { POLYOracle: ${POLYOracle} STFactory: ${STFactory.address} + GeneralTransferManagerLogic: ${GeneralTransferManagerLogic.address} GeneralTransferManagerFactory: ${GeneralTransferManagerFactory.address} GeneralPermissionManagerFactory: ${GeneralPermissionManagerFactory.address} CappedSTOFactory: ${CappedSTOFactory.address} USDTieredSTOFactory: ${USDTieredSTOFactory.address} - USDTieredSTOProxyFactory: ${USDTieredSTOProxyFactory.address} + USDTieredSTOLogic: ${USDTieredSTOLogic.address} CountTransferManagerFactory: ${CountTransferManagerFactory.address} PercentageTransferManagerFactory: ${PercentageTransferManagerFactory.address} ManualApprovalTransferManagerFactory: ${ManualApprovalTransferManagerFactory.address} + EtherDividendCheckpointLogic: ${EtherDividendCheckpointLogic.address} + ERC20DividendCheckpointLogic: ${ERC20DividendCheckpointLogic.address} EtherDividendCheckpointFactory: ${EtherDividendCheckpointFactory.address} ERC20DividendCheckpointFactory: ${ERC20DividendCheckpointFactory.address} + LockUpTransferManagerFactory: ${LockUpTransferManagerFactory.address} + BlacklistTransferManagerFactory: ${BlacklistTransferManagerFactory.address} + VolumeRestrictionTMFactory: ${VolumeRestrictionTMFactory.address} + VolumeRestrictionTMLogic: ${VolumeRestrictionTMLogic.address} + VestingEscrowWalletFactory: ${VestingEscrowWalletFactory.address} + VestingEscrowWalletLogic: ${VestingEscrowWalletLogic.address} --------------------------------------------------------------------------------- `); console.log('\n'); diff --git a/package.json b/package.json index 804b7485d..4d905c166 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "polymath-core", - "version": "2.0.0", + "version": "2.1.0", "description": "Polymath Network Core Smart Contracts", "main": "truffle.js", "directories": { @@ -8,6 +8,7 @@ }, "scripts": { "test": "scripts/test.sh 2> /dev/null", + "deploy-kovan": "scripts/deploy.sh", "wintest": "scripts\\wintest.cmd", "wincov": "scripts\\wincov.cmd", "docs": "scripts/docs.sh", @@ -63,39 +64,35 @@ "babel-register": "6.26.0", "bignumber.js": "5.0.0", "chalk": "^2.4.1", - "coveralls": "^3.0.1", - "ethereumjs-testrpc": "^6.0.3", - "ethers": "^4.0.7", + "coveralls": "^3.0.2", + "csv-parse": "^4.2.0", + "ethers": "^4.0.20", "fs": "0.0.2", "openzeppelin-solidity": "1.10.0", - "readline-sync": "^1.4.9", - "request": "^2.88.0", - "request-promise": "^4.2.2", - "shelljs": "^0.8.2", - "solc": "^0.4.24", - "truffle-contract": "^3.0.4", - "truffle-hdwallet-provider-privkey": "0.2.0", + "prompt": "^1.0.0", + "truffle-hdwallet-provider-privkey": "^0.3.0", "web3": "1.0.0-beta.34" }, "devDependencies": { - "@soldoc/soldoc": "^0.4.3", - "eslint": "^5.8.0", + "eslint": "^5.10.0", "eslint-config-standard": "^12.0.0", - "eslint-plugin-import": "^2.10.0", + "eslint-plugin-import": "^2.14.0", "eslint-plugin-node": "^8.0.0", "eslint-plugin-promise": "^4.0.1", "eslint-plugin-standard": "^4.0.0", "ethereum-bridge": "^0.6.1", "ethereumjs-abi": "^0.6.5", - "fast-csv": "^2.4.1", - "ganache-cli": "^6.1.8", + "ganache-cli": "^6.2.5", "mocha-junit-reporter": "^1.18.0", - "prettier": "^1.14.3", - "sol-merger": "^0.1.2", + "prettier": "^1.15.3", + "request": "^2.88.0", + "request-promise": "^4.2.2", + "sol-merger": "^0.1.3", "solidity-coverage": "^0.5.11", - "solidity-docgen": "^0.1.1", - "solium": "^1.1.6", + "solidity-docgen": "^0.1.0", + "solium": "^1.1.8", "truffle": "4.1.14", + "truffle-hdwallet-provider": "^1.0.3", "truffle-wallet-provider": "0.0.5" }, "greenkeeper": { diff --git a/scripts/compareStorageLayout.js b/scripts/compareStorageLayout.js new file mode 100644 index 000000000..c339bf095 --- /dev/null +++ b/scripts/compareStorageLayout.js @@ -0,0 +1,139 @@ +const fs = require('fs'); +const _ = require('underscore'); +const solc = require('solc'); +const prompt = require('prompt'); +const path = require('path'); +const util = require('util'); +const exec = util.promisify(require('child_process').exec); + +prompt.start(); + +prompt.get(['LogicContract', 'ProxyContract'], async(err, result) => { + + let logicContract; + let proxyContract; + + const fileList = walkSync('./contracts', []); + + let paths = findPath(result.LogicContract, result.ProxyContract, fileList); + + if (paths.length == 2) { + + console.log("Contracts exists \n"); + + await flatContracts(paths); + + if (path.basename(paths[0]) === result.LogicContract) { + logicContract = fs.readFileSync(`./flat/${path.basename(paths[0])}`, 'utf8'); + } else { + logicContract = fs.readFileSync(`./flat/${path.basename(paths[1])}`, 'utf8'); + } + if (path.basename(paths[0]) === result.ProxyContract) { + proxyContract = fs.readFileSync(`./flat/${path.basename(paths[0])}`, 'utf8'); + } else { + proxyContract = fs.readFileSync(`./flat/${path.basename(paths[1])}`, 'utf8'); + } + + let logicInput = { + 'contracts': logicContract + } + let proxyInput = { + 'contracts': proxyContract + } + + console.log(compareStorageLayouts(parseContract(logicInput), parseContract(proxyInput))); + + } else { + console.log("Contracts doesn't exists"); + } +}); + +function traverseAST(_input, _elements) { + if(_input.children) { + for(var i=0;i<_input.children.length;i++) { + traverseAST(_input.children[i], _elements); + } + } + _elements.push(_input); +} + +function compareStorageLayouts(logicLayout, proxyLayout) { + function makeComp(x) { + return [x.constant, x.name, x.stateVariable, x.storageLocation, x.type, x.value, x.visibility].join(':'); + } + // if(newLayout.length < oldLayout.length) return false; + for(var i=0; i < logicLayout.length; i++) { + const a = logicLayout[i].attributes; + const comp1 = makeComp(a) + console.log(comp1); + const b = proxyLayout[i].attributes; + const comp2 = makeComp(b); + console.log(comp2); + if(comp1 != comp2) { + return false; + } + } + return true; +} + +function parseContract(input) { + + var output = solc.compile({ sources: input }, 1, _.noop); + const elements = []; + const AST = output.sources.contracts.AST; + // console.log(AST); + traverseAST(AST, elements); + // console.log(elements); + + // filter out all Contract Definitions + const contractDefinitions = _.filter(elements, (e,i) => e.name == 'ContractDefinition'); + + // filter out all linearizedBaseContracts + // pick the last one as the last contract always has the full inheritance + const linearizedBaseContracts = _.last(_.map(contractDefinitions, e => e.attributes.linearizedBaseContracts)); + + // get all stateVariables + const stateVariables = _.filter(elements, e => e.attributes && e.attributes.stateVariable ) + + // group them by scope + const stateVariableMap = _.groupBy(stateVariables, e => e.attributes.scope); + + orderedStateVariables = _.reduceRight(linearizedBaseContracts, (a, b) => { + return a.concat(stateVariableMap[b] || []) + }, []); + return orderedStateVariables; +} + +var walkSync = function(dir, filelist) { + files = fs.readdirSync(dir); + filelist = filelist || []; + files.forEach(function(file) { + if (fs.statSync(path.join(dir, file)).isDirectory()) { + filelist = walkSync(path.join(dir, file), filelist); + } + else { + filelist.push(path.join(dir, file)); + } + }); + return filelist; +}; + +var findPath = function(logicContractName, proxyContractName, fileList) { + let paths = new Array(); + for (let i =0; i < fileList.length; i++) { + if ((logicContractName === path.basename(fileList[i]) || logicContractName === (path.basename(fileList[i])).split(".")[0]) || + (proxyContractName === path.basename(fileList[i]) || proxyContractName === (path.basename(fileList[i])).split(".")[0])) { + paths.push(fileList[i]); + } + } + return paths; +} + +async function flatContracts(_paths, _logic) { + let promises = new Array(); + for (let i = 0; i< _paths.length; i++) { + promises.push(await exec(`./node_modules/.bin/sol-merger ${_paths[i]} ./flat`)); + } + await Promise.all(promises); +} + diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 000000000..f961fb5fd --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +node_modules/.bin/truffle deploy --network kovan +address=$(grep build/contracts/PolymathRegistry.json -e "\"address\":" | grep -o "0[0-9a-z]*") +commitId=$(git rev-parse HEAD) +branchName=$(git rev-parse --abbrev-ref HEAD) +commitName=$(git log -1 --pretty=%B) +data='{"text":"Somebody merged a new pull request!\nBranch Name: '$branchName'\nCommit Name: '$commitName'\nCommit ID: '$commitId'\nPolymath Registry Address(Kovan): '$address'"}' +curl -X POST -H 'Content-type: application/json' --data "$data" ${SLACK_DEVNOTIFACTIONS_WH} \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh index 9f595d0ba..0d8fa4505 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -56,7 +56,7 @@ start_testrpc() { --account="0x2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501209,1000000000000000000000000" ) - if [ "$COVERAGE" = true ]; then + if [ "$COVERAGE" = true ] || [ "$TRAVIS_PULL_REQUEST" > 0 ] && [ "$NOT_FORK" != true ]; then node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --port "$testrpc_port" "${accounts[@]}" > /dev/null & else node_modules/.bin/ganache-cli --gasLimit 8000000 "${accounts[@]}" > /dev/null & @@ -88,11 +88,15 @@ else fi fi -if [ "$COVERAGE" = true ]; then +if [ "$COVERAGE" = true ] || [ "$TRAVIS_PULL_REQUEST" > 0 ] && [ "$NOT_FORK" != true ]; then curl -o node_modules/solidity-coverage/lib/app.js https://raw.githubusercontent.com/maxsam4/solidity-coverage/relative-path/lib/app.js - node_modules/.bin/solidity-coverage if [ "$CIRCLECI" = true ]; then - cat coverage/lcov.info | node_modules/.bin/coveralls + rm truffle-config.js + mv truffle-ci.js truffle-config.js + fi + node_modules/.bin/solidity-coverage + if [ "$CIRCLECI" = true ] || [ "$TRAVIS_PULL_REQUEST" > 0 ] && [ "$NOT_FORK" != true ]; then + cat coverage/lcov.info | node_modules/.bin/coveralls || echo 'Failed to report coverage to Coveralls' fi else if [ "$CIRCLECI" = true ]; then # using mocha junit reporter for parallelism in CircleCI @@ -109,4 +113,4 @@ else else node_modules/.bin/truffle test `find test/*.js ! -name a_poly_oracle.js -and ! -name s_v130_to_v140_upgrade.js` fi -fi \ No newline at end of file +fi diff --git a/scripts/tokenInfo-v1.js b/scripts/tokenInfo-v1.js index 7989b105e..ea14a7efe 100644 --- a/scripts/tokenInfo-v1.js +++ b/scripts/tokenInfo-v1.js @@ -12,7 +12,7 @@ async function getTokens() { let logs = await getLogsFromEtherscan(securityTokenRegistry.options.address, web3.utils.hexToNumber('0x5C5C18'), 'latest', 'LogNewSecurityToken(string,address,address)'); for (let i = 0; i < logs.length; i++) { - let tokenAddress = '0x' + logs[i].topics[1].slice(26,66) + let tokenAddress = '0x' + logs[i].topics[1].slice(26, 66) await getInfo(tokenAddress); } } diff --git a/test/b_capped_sto.js b/test/b_capped_sto.js index f9238ffd9..ea4317638 100644 --- a/test/b_capped_sto.js +++ b/test/b_capped_sto.js @@ -2,11 +2,12 @@ import latestTime from "./helpers/latestTime"; import { duration, ensureException, promisifyLogWatch, latestBlock } from "./helpers/utils"; import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeModuleCall } from "./helpers/encodeCall"; -import { setUpPolymathNetwork, deployGPMAndVerifyed } from "./helpers/createInstances"; +import { setUpPolymathNetwork, deployGPMAndVerifyed, deployDummySTOAndVerifyed } from "./helpers/createInstances"; import { catchRevert } from "./helpers/exceptions"; const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); const CappedSTO = artifacts.require("./CappedSTO.sol"); +const DummySTO = artifacts.require("./DummySTO.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); @@ -54,6 +55,7 @@ contract("CappedSTO", accounts => { let I_SecurityToken_POLY; let I_CappedSTO_Array_ETH = []; let I_CappedSTO_Array_POLY = []; + let I_DummySTO; let I_PolyToken; let I_PolymathRegistry; let I_STRProxied; @@ -86,7 +88,7 @@ contract("CappedSTO", accounts => { let startTime_ETH2; let endTime_ETH2; const cap = web3.utils.toWei("10000"); - const rate = 1000; + const rate = web3.utils.toWei("1000"); const E_fundRaiseType = 0; const address_zero = "0x0000000000000000000000000000000000000000"; @@ -97,7 +99,7 @@ contract("CappedSTO", accounts => { let blockNo; const P_cap = web3.utils.toWei("50000"); const P_fundRaiseType = 1; - const P_rate = 5; + const P_rate = web3.utils.toWei("5"); const cappedSTOSetupCost = web3.utils.toWei("20000", "ether"); const maxCost = cappedSTOSetupCost; const STOParameters = ["uint256", "uint256", "uint256", "uint256", "uint8[]", "address"]; @@ -130,7 +132,7 @@ contract("CappedSTO", accounts => { // STEP 5: Deploy the GeneralDelegateManagerFactory [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); - + // STEP 6: Deploy the CappedSTOFactory I_CappedSTOFactory = await CappedSTOFactory.new(I_PolyToken.address, cappedSTOSetupCost, 0, 0, { from: token_owner }); @@ -298,7 +300,7 @@ contract("CappedSTO", accounts => { assert.equal(await I_CappedSTO_Array_ETH[0].startTime.call(), startTime_ETH1, "STO Configuration doesn't set as expected"); assert.equal(await I_CappedSTO_Array_ETH[0].endTime.call(), endTime_ETH1, "STO Configuration doesn't set as expected"); assert.equal((await I_CappedSTO_Array_ETH[0].cap.call()).toNumber(), cap, "STO Configuration doesn't set as expected"); - assert.equal(await I_CappedSTO_Array_ETH[0].rate.call(), rate, "STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_ETH[0].rate.call()).toNumber(), rate, "STO Configuration doesn't set as expected"); assert.equal( await I_CappedSTO_Array_ETH[0].fundRaiseTypes.call(E_fundRaiseType), true, @@ -338,16 +340,6 @@ contract("CappedSTO", accounts => { ); }); - it("Should buy the tokens -- Failed due to wrong granularity", async () => { - await catchRevert( - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("0.1111", "ether") - }) - ); - }); - it("Should Buy the tokens", async () => { blockNo = latestBlock(); fromTime = latestTime(); @@ -427,17 +419,8 @@ contract("CappedSTO", accounts => { assert.isFalse(await I_CappedSTO_Array_ETH[0].paused.call()); }); - it("Should buy the tokens -- Failed due to wrong granularity", async () => { - await catchRevert( - web3.eth.sendTransaction({ - from: account_investor1, - to: I_CappedSTO_Array_ETH[0].address, - value: web3.utils.toWei("0.1111", "ether") - }) - ); - }); - - it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { + it("Should buy the granular unit tokens and refund pending amount", async () => { + await I_SecurityToken_ETH.changeGranularity(10 ** 21, {from: token_owner}); let tx = await I_GeneralTransferManager.modifyWhitelist( account_investor2, fromTime, @@ -448,15 +431,26 @@ contract("CappedSTO", accounts => { from: account_issuer } ); - assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); + const initBalance = BigNumber(await web3.eth.getBalance(account_investor2)); + tx = await I_CappedSTO_Array_ETH[0].buyTokens(account_investor2, {from: account_investor2, value: web3.utils.toWei("1.5", "ether"), gasPrice: 1}); + const finalBalance = BigNumber(await web3.eth.getBalance(account_investor2)); + assert.equal(finalBalance.add(BigNumber(tx.receipt.gasUsed)).add(web3.utils.toWei("1", "ether")).toNumber(), initBalance.toNumber()); + await I_SecurityToken_ETH.changeGranularity(1, {from: token_owner}); + assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 2); + + assert.equal((await I_SecurityToken_ETH.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1000); + }); + + it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { + // Fallback transaction await web3.eth.sendTransaction({ from: account_investor2, to: I_CappedSTO_Array_ETH[0].address, gas: 2100000, - value: web3.utils.toWei("9", "ether") + value: web3.utils.toWei("8", "ether") }); assert.equal((await I_CappedSTO_Array_ETH[0].getRaised.call(ETH)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10); @@ -554,7 +548,7 @@ contract("CappedSTO", accounts => { assert.equal(await I_CappedSTO_Array_ETH[1].startTime.call(), startTime_ETH2, "STO Configuration doesn't set as expected"); assert.equal(await I_CappedSTO_Array_ETH[1].endTime.call(), endTime_ETH2, "STO Configuration doesn't set as expected"); assert.equal((await I_CappedSTO_Array_ETH[1].cap.call()).toNumber(), cap, "STO Configuration doesn't set as expected"); - assert.equal(await I_CappedSTO_Array_ETH[1].rate.call(), rate, "STO Configuration doesn't set as expected"); + assert.equal((await I_CappedSTO_Array_ETH[1].rate.call()).toNumber(), rate, "STO Configuration doesn't set as expected"); assert.equal( await I_CappedSTO_Array_ETH[1].fundRaiseTypes.call(E_fundRaiseType), true, @@ -793,8 +787,8 @@ contract("CappedSTO", accounts => { await I_CappedSTO_Array_POLY[0].unpause({ from: account_issuer }); }); - - it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { + it("Should buy the granular unit tokens and charge only required POLY", async () => { + await I_SecurityToken_POLY.changeGranularity(10 ** 22, {from: token_owner}); let tx = await I_GeneralTransferManager.modifyWhitelist( account_investor2, P_fromTime, @@ -806,15 +800,25 @@ contract("CappedSTO", accounts => { gas: 500000 } ); - + console.log((await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber()); assert.equal(tx.logs[0].args._investor, account_investor2, "Failed in adding the investor in whitelist"); - await I_PolyToken.getTokens(10000 * Math.pow(10, 18), account_investor2); - await I_PolyToken.approve(I_CappedSTO_Array_POLY[0].address, 9000 * Math.pow(10, 18), { from: account_investor2 }); + const initRaised = (await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(); + tx = await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(3000 * Math.pow(10, 18), { from: account_investor2 }); + await I_SecurityToken_POLY.changeGranularity(1, {from: token_owner}); + assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), initRaised + 2000); //2000 this call, 1000 earlier + assert.equal((await I_PolyToken.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 8000); + assert.equal( + (await I_SecurityToken_POLY.balanceOf(account_investor2)).dividedBy(new BigNumber(10).pow(18)).toNumber(), + 10000 + ); + }); + + it("Should restrict to buy tokens after hiting the cap in second tx first tx pass", async () => { // buyTokensWithPoly transaction - await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(9000 * Math.pow(10, 18), { from: account_investor2 }); + await I_CappedSTO_Array_POLY[0].buyTokensWithPoly(7000 * Math.pow(10, 18), { from: account_investor2 }); assert.equal((await I_CappedSTO_Array_POLY[0].getRaised.call(POLY)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 10000); @@ -848,6 +852,58 @@ contract("CappedSTO", accounts => { }); }); + describe("Check that we can reclaim ETH and ERC20 tokens from an STO", async () => { + //xxx + it("should attach a dummy STO", async () => { + let I_DummySTOFactory; + [I_DummySTOFactory] = await deployDummySTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + const DummySTOParameters = ["uint256", "uint256", "uint256", "string"]; + let startTime = latestTime() + duration.days(1); + let endTime = startTime + duration.days(30); + const cap = web3.utils.toWei("10000"); + const dummyBytesSig = encodeModuleCall(DummySTOParameters, [startTime, endTime, cap, "Hello"]); + const tx = await I_SecurityToken_ETH.addModule(I_DummySTOFactory.address, dummyBytesSig, maxCost, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0], stoKey, `Wrong module type added`); + assert.equal( + web3.utils.hexToString(tx.logs[2].args._name), + "DummySTO", + `Wrong STO module added` + ); + I_DummySTO = DummySTO.at(tx.logs[2].args._module); + }); + it("should send some funds and ERC20 to the DummySTO", async () => { + let tx = await web3.eth.sendTransaction({ + from: account_investor1, + to: I_DummySTO.address, + gas: 2100000, + value: web3.utils.toWei("1", "ether") + }); + let dummyETH = BigNumber(await web3.eth.getBalance(I_DummySTO.address)); + assert.equal(dummyETH.toNumber(), web3.utils.toWei("1", "ether")); + await I_PolyToken.getTokens(web3.utils.toWei("2", "ether"), I_DummySTO.address); + let dummyPOLY = BigNumber(await I_PolyToken.balanceOf(I_DummySTO.address)); + assert.equal(dummyPOLY.toNumber(), web3.utils.toWei("2", "ether")); + }); + + it("should reclaim ETH and ERC20 from STO", async () => { + let initialIssuerETH = BigNumber(await web3.eth.getBalance(token_owner)); + let initialIssuerPOLY = BigNumber(await I_PolyToken.balanceOf(token_owner)); + await catchRevert(I_DummySTO.reclaimERC20(I_PolyToken.address, {from: account_polymath, gasPrice: 0})); + await catchRevert(I_DummySTO.reclaimETH( {from: account_polymath, gasPrice: 0})); + let tx = await I_DummySTO.reclaimERC20(I_PolyToken.address, {from: token_owner, gasPrice: 0}); + let tx2 = await I_DummySTO.reclaimETH({from: token_owner, gasPrice: 0}); + let finalIssuerETH = BigNumber(await web3.eth.getBalance(token_owner)); + let finalIssuerPOLY = BigNumber(await I_PolyToken.balanceOf(token_owner)); + assert.equal(finalIssuerETH.sub(initialIssuerETH).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal(finalIssuerPOLY.sub(initialIssuerPOLY).toNumber(), web3.utils.toWei("2", "ether")); + let dummyETH = BigNumber(await web3.eth.getBalance(I_DummySTO.address)); + assert.equal(dummyETH.toNumber(), 0); + let dummyPOLY = BigNumber(await I_PolyToken.balanceOf(I_DummySTO.address)); + assert.equal(dummyPOLY.toNumber(), 0); + }); + }); + + describe("Test cases for the CappedSTOFactory", async () => { it("should get the exact details of the factory", async () => { assert.equal((await I_CappedSTOFactory.getSetupCost.call()).toNumber(), cappedSTOSetupCost); @@ -855,7 +911,7 @@ contract("CappedSTO", accounts => { assert.equal(web3.utils.hexToString(await I_CappedSTOFactory.getName.call()), "CappedSTO", "Wrong Module added"); assert.equal( await I_CappedSTOFactory.description.call(), - "Use to collects the funds and once the cap is reached then investment will be no longer entertained", + "This smart contract creates a maximum number of tokens (i.e. hard cap) which the total aggregate of tokens acquired by all investors cannot exceed. Security tokens are sent to the investor upon reception of the funds (ETH or POLY), and any security tokens left upon termination of the offering will not be minted.", "Wrong Module added" ); assert.equal(await I_CappedSTOFactory.title.call(), "Capped STO", "Wrong Module added"); @@ -866,7 +922,7 @@ contract("CappedSTO", accounts => { ); let tags = await I_CappedSTOFactory.getTags.call(); assert.equal(web3.utils.hexToString(tags[0]), "Capped"); - assert.equal(await I_CappedSTOFactory.version.call(), "1.0.0"); + assert.equal(await I_CappedSTOFactory.version.call(), "2.1.0"); }); it("Should fail to change the title -- bad owner", async () => { @@ -993,7 +1049,7 @@ contract("CappedSTO", accounts => { it("Should successfully invest in second STO", async () => { const polyToInvest = 1000; - const stToReceive = polyToInvest * P_rate; + const stToReceive = (polyToInvest * P_rate)/Math.pow(10, 18); await I_PolyToken.getTokens(polyToInvest * Math.pow(10, 18), account_investor3); diff --git a/test/d_count_transfer_manager.js b/test/d_count_transfer_manager.js index 1a1e7717a..3107c4bf7 100644 --- a/test/d_count_transfer_manager.js +++ b/test/d_count_transfer_manager.js @@ -39,7 +39,9 @@ contract("CountTransferManager", accounts => { let I_CountTransferManagerFactory; let I_GeneralPermissionManager; let I_CountTransferManager; + let I_CountTransferManager2; let I_GeneralTransferManager; + let I_GeneralTransferManager2; let I_ExchangeTransferManager; let I_ModuleRegistry; let I_ModuleRegistryProxy; @@ -49,12 +51,14 @@ contract("CountTransferManager", accounts => { let I_SecurityTokenRegistry; let I_STFactory; let I_SecurityToken; + let I_SecurityToken2; let I_PolyToken; let I_PolymathRegistry; // SecurityToken Details const name = "Team"; const symbol = "sap"; + const symbol2 = "sapp"; const tokenDetails = "This is equity type of issuance"; const decimals = 18; const contact = "team@polymath.network"; @@ -81,6 +85,7 @@ contract("CountTransferManager", accounts => { account_investor1 = accounts[7]; account_investor2 = accounts[8]; account_investor3 = accounts[9]; + account_investor4 = accounts[6]; // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -347,6 +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 the factory", async () => { it("should get the exact details of the factory", async () => { diff --git a/test/e_erc20_dividends.js b/test/e_erc20_dividends.js index 26adb85b0..692916f67 100644 --- a/test/e_erc20_dividends.js +++ b/test/e_erc20_dividends.js @@ -3,6 +3,7 @@ import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployERC20DividendAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; +import { encodeModuleCall } from "./helpers/encodeCall"; const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); @@ -18,6 +19,7 @@ contract("ERC20DividendCheckpoint", accounts => { let account_polymath; let account_issuer; let token_owner; + let wallet; let account_investor1; let account_investor2; let account_investor3; @@ -76,6 +78,8 @@ contract("ERC20DividendCheckpoint", accounts => { const one_address = '0x0000000000000000000000000000000000000001'; + const DividendParameters = ["address"]; + before(async () => { // Accounts setup account_polymath = accounts[0]; @@ -89,7 +93,8 @@ contract("ERC20DividendCheckpoint", accounts => { account_investor4 = accounts[9]; account_temp = accounts[2]; account_manager = accounts[5]; - + wallet = accounts[3]; + // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -157,8 +162,9 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should successfully attach the ERC20DividendCheckpoint with the security token - fail insufficient payment", async () => { + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); await catchRevert( - I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) ); @@ -168,7 +174,8 @@ contract("ERC20DividendCheckpoint", accounts => { let snapId = await takeSnapshot(); await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); - const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(P_ERC20DividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "ERC20DividendCheckpoint doesn't get deployed"); @@ -182,7 +189,8 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_ERC20DividendCheckpointFactory.address, "", 0, 0, { from: token_owner }); + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(I_ERC20DividendCheckpointFactory.address, bytesDividend, 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "ERC20DividendCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), @@ -330,6 +338,12 @@ contract("ERC20DividendCheckpoint", accounts => { ); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 1, "Dividend should be created at checkpoint 1"); assert.equal(tx.logs[0].args._name.toString(), dividendName, "Dividend name incorrect in event"); + let data = await I_ERC20DividendCheckpoint.getDividendsData(); + assert.equal(data[1][0].toNumber(), maturity, "maturity match"); + assert.equal(data[2][0].toNumber(), expiry, "expiry match"); + assert.equal(data[3][0].toNumber(), web3.utils.toWei("1.5", "ether"), "amount match"); + assert.equal(data[4][0].toNumber(), 0, "claimed match"); + assert.equal(data[5][0], dividendName, "dividendName match"); }); it("Investor 1 transfers his token balance to investor 2", async () => { @@ -412,6 +426,7 @@ contract("ERC20DividendCheckpoint", accounts => { dividendName, { from: token_owner } ); + console.log("Gas used w/ no exclusions: " + tx.receipt.gasUsed); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 2, "Dividend should be created at checkpoint 1"); }); @@ -490,6 +505,7 @@ contract("ERC20DividendCheckpoint", accounts => { let addresses = []; addresses.push(account_temp); while (limit--) addresses.push(limit); + console.log(addresses.length); await catchRevert(I_ERC20DividendCheckpoint.setDefaultExcluded(addresses, { from: token_owner })); }); @@ -506,7 +522,9 @@ contract("ERC20DividendCheckpoint", accounts => { dividendName, { from: token_owner } ); + console.log("Gas used w/ max exclusions - default: " + tx.receipt.gasUsed); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 3, "Dividend should be created at checkpoint 2"); + assert.equal((await I_ERC20DividendCheckpoint.isExcluded.call(account_temp, tx.logs[0].args._dividendIndex)), true, "account_temp is excluded"); }); it("should investor 3 claims dividend - fail bad index", async () => { @@ -525,6 +543,16 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), web3.utils.toWei("7", "ether")); + let info = await I_ERC20DividendCheckpoint.getDividendProgress.call(2); + console.log(info); + assert.equal(info[0][1], account_temp, "account_temp"); + assert.equal(info[1][1], false, "account_temp is not claimed"); + assert.equal(info[2][1], true, "account_temp is excluded"); + assert.equal(info[3][1], 0, "account_temp is not withheld"); + assert.equal(info[0][2], account_investor3, "account_investor3"); + assert.equal(info[1][2], true, "account_investor3 is claimed"); + assert.equal(info[2][2], false, "account_investor3 is claimed"); + assert.equal(info[3][2], 0, "account_investor3 is not withheld"); }); it("should investor 3 claims dividend - fails already claimed", async () => { @@ -635,10 +663,10 @@ contract("ERC20DividendCheckpoint", accounts => { ); }); - it("Set withholding tax of 20% on account_temp and 10% on investor2", async () => { + it("Set withholding tax of 20% on account_temp and 100% on investor2", async () => { await I_ERC20DividendCheckpoint.setWithholding( [account_temp, account_investor2], - [BigNumber(20 * 10 ** 16), BigNumber(10 * 10 ** 16)], + [BigNumber(20 * 10 ** 16), BigNumber(100 * 10 ** 16)], { from: token_owner } ); }); @@ -699,7 +727,7 @@ contract("ERC20DividendCheckpoint", accounts => { dividendName, { from: token_owner } ); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 3"); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), 4, "Dividend should be created at checkpoint 4"); }); it("Should not create new dividend with duplicate exclusion", async () => { @@ -772,25 +800,42 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(dividendAmount3[0].toNumber(), web3.utils.toWei("7", "ether")); assert.equal(dividendAmount_temp[0].toNumber(), web3.utils.toWei("1", "ether")); assert.equal(dividendAmount1[1].toNumber(), web3.utils.toWei("0", "ether")); - assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("0.2", "ether")); + assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("2", "ether")); assert.equal(dividendAmount3[1].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount_temp[1].toNumber(), web3.utils.toWei("0.2", "ether")); }); + it("Pause and unpause the dividend contract", async () => { + await catchRevert(I_ERC20DividendCheckpoint.pause({from: account_polymath})); + let tx = await I_ERC20DividendCheckpoint.pause({from: token_owner}); + await catchRevert(I_ERC20DividendCheckpoint.pullDividendPayment(3, { from: account_investor2, gasPrice: 0 })); + await catchRevert(I_ERC20DividendCheckpoint.unpause({from: account_polymath})); + tx = await I_ERC20DividendCheckpoint.unpause({from: token_owner}); + }); + + it("Investor 2 claims dividend", async () => { let investor1Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3Balance = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); let tempBalance = new BigNumber(await web3.eth.getBalance(account_temp)); - await I_ERC20DividendCheckpoint.pullDividendPayment(3, { from: account_investor2, gasPrice: 0 }); + let _blockNo = latestBlock(); + let tx = await I_ERC20DividendCheckpoint.pullDividendPayment(3, { from: account_investor2, gasPrice: 0 }); let investor1BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); let tempBalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_temp)); assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei("1.8", "ether")); + // Full amount is in withheld tax + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); + //Check tx contains event... + const log = await promisifyLogWatch(I_ERC20DividendCheckpoint.ERC20DividendClaimed({ from: _blockNo }), 1); + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._payee, account_investor2); + assert.equal(log.args._withheld.toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(log.args._amount.toNumber(), web3.utils.toWei("2", "ether")); }); it("Should issuer pushes temp investor - investor1 excluded", async () => { @@ -798,7 +843,10 @@ contract("ERC20DividendCheckpoint", accounts => { let investor2BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); let tempBalanceAfter1 = new BigNumber(await I_PolyToken.balanceOf(account_temp)); + // Issuer can still push payments when contract is paused + let tx = await I_ERC20DividendCheckpoint.pause({from: token_owner}); await I_ERC20DividendCheckpoint.pushDividendPaymentToAddresses(3, [account_temp, account_investor1], { from: token_owner }); + tx = await I_ERC20DividendCheckpoint.unpause({from: token_owner}); let investor1BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor1)); let investor2BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor2)); let investor3BalanceAfter2 = new BigNumber(await I_PolyToken.balanceOf(account_investor3)); @@ -823,10 +871,88 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Issuer reclaims withholding tax", async () => { - let issuerBalance = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let info = await I_ERC20DividendCheckpoint.getDividendProgress.call(3); + + console.log("Address:"); + console.log(info[0][0]); + console.log(info[0][1]); + console.log(info[0][2]); + console.log(info[0][3]); + + console.log("Claimed:"); + console.log(info[1][0]); + console.log(info[1][1]); + console.log(info[1][2]); + console.log(info[1][3]); + + console.log("Excluded:"); + console.log(info[2][0]); + console.log(info[2][1]); + console.log(info[2][2]); + console.log(info[2][3]); + + console.log("Withheld:"); + console.log(info[3][0].toNumber()); + console.log(info[3][1].toNumber()); + console.log(info[3][2].toNumber()); + console.log(info[3][3].toNumber()); + + console.log("Claimed:"); + console.log(info[4][0].toNumber()); + console.log(info[4][1].toNumber()); + console.log(info[4][2].toNumber()); + console.log(info[4][3].toNumber()); + + console.log("Balance:"); + console.log(info[5][0].toNumber()); + console.log(info[5][1].toNumber()); + console.log(info[5][2].toNumber()); + console.log(info[5][3].toNumber()); + + assert.equal(info[0][0], account_investor1, "account match"); + assert.equal(info[0][1], account_investor2, "account match"); + assert.equal(info[0][2], account_temp, "account match"); + assert.equal(info[0][3], account_investor3, "account match"); + + assert.equal(info[3][0].toNumber(), 0, "withheld match"); + assert.equal(info[3][1].toNumber(), web3.utils.toWei("2", "ether"), "withheld match"); + assert.equal(info[3][2].toNumber(), web3.utils.toWei("0.2", "ether"), "withheld match"); + assert.equal(info[3][3].toNumber(), 0, "withheld match"); + + assert.equal(info[4][0].toNumber(), 0, "excluded"); + assert.equal(info[4][1].toNumber(), web3.utils.toWei("0", "ether"), "claim match"); + assert.equal(info[4][2].toNumber(), web3.utils.toWei("0.8", "ether"), "claim match"); + assert.equal(info[4][3].toNumber(), web3.utils.toWei("7", "ether"), "claim match"); + + assert.equal(info[5][0].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor1, 4)).toNumber(), "balance match"); + assert.equal(info[5][1].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor2, 4)).toNumber(), "balance match"); + assert.equal(info[5][2].toNumber(), (await I_SecurityToken.balanceOfAt(account_temp, 4)).toNumber(), "balance match"); + assert.equal(info[5][3].toNumber(), (await I_SecurityToken.balanceOfAt(account_investor3, 4)).toNumber(), "balance match"); + + let dividend = await I_ERC20DividendCheckpoint.dividends.call(3); + console.log("totalWithheld: " + dividend[8].toNumber()); + console.log("totalWithheldWithdrawn: " + dividend[9].toNumber()); + assert.equal(dividend[8].toNumber(), web3.utils.toWei("2.2", "ether")); + assert.equal(dividend[9].toNumber(), 0); + let issuerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); await I_ERC20DividendCheckpoint.withdrawWithholding(3, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await I_PolyToken.balanceOf(token_owner)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.4", "ether")); + let issuerBalanceAfter = new BigNumber(await I_PolyToken.balanceOf(wallet)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("2.2", "ether")); + dividend = await I_ERC20DividendCheckpoint.dividends.call(3); + console.log("totalWithheld: " + dividend[8].toNumber()); + console.log("totalWithheldWithdrawn: " + dividend[9].toNumber()); + assert.equal(dividend[8].toNumber(), web3.utils.toWei("2.2", "ether")); + assert.equal(dividend[9].toNumber(), web3.utils.toWei("2.2", "ether")); + }); + + it("Issuer changes wallet address", async () => { + await catchRevert(I_ERC20DividendCheckpoint.changeWallet(token_owner, { from: wallet })); + await I_ERC20DividendCheckpoint.changeWallet(token_owner, {from: token_owner}); + let newWallet = await I_ERC20DividendCheckpoint.wallet.call(); + assert.equal(newWallet, token_owner, "Wallets match"); + await I_ERC20DividendCheckpoint.changeWallet(wallet, {from: token_owner}); + newWallet = await I_ERC20DividendCheckpoint.wallet.call(); + assert.equal(newWallet, wallet, "Wallets match"); }); it("Issuer unable to reclaim dividend (expiry not passed)", async () => { @@ -843,9 +969,9 @@ contract("ERC20DividendCheckpoint", accounts => { }); it("Issuer is able to reclaim dividend after expiry", async () => { - let tokenOwnerBalance = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let tokenOwnerBalance = new BigNumber(await I_PolyToken.balanceOf(wallet)); await I_ERC20DividendCheckpoint.reclaimDividend(3, { from: token_owner, gasPrice: 0 }); - let tokenOwnerAfter = new BigNumber(await I_PolyToken.balanceOf(token_owner)); + let tokenOwnerAfter = new BigNumber(await I_PolyToken.balanceOf(wallet)); assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei("7", "ether")); }); @@ -867,17 +993,12 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(index.length, 0); }); - it("Get the init data", async () => { - let tx = await I_ERC20DividendCheckpoint.getInitFunction.call(); - assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ""), 0); - }); - it("Should get the listed permissions", async () => { let tx = await I_ERC20DividendCheckpoint.getPermissions.call(); assert.equal(tx.length, 2); }); - it("should registr a delegate", async () => { + it("should register a delegate", async () => { [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); let tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); @@ -970,7 +1091,7 @@ contract("ERC20DividendCheckpoint", accounts => { let expiry = latestTime() + duration.days(10); let exclusions = [1]; let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); - await I_SecurityToken.createCheckpoint({ from: token_owner }); + await I_SecurityToken.createCheckpoint({ from: token_owner }); await catchRevert(I_ERC20DividendCheckpoint.createDividendWithCheckpointAndExclusions( maturity, expiry, @@ -1056,13 +1177,33 @@ contract("ERC20DividendCheckpoint", accounts => { dividendName, { from: account_manager } ); - assert.equal(tx.logs[0].args._checkpointId.toNumber(), 8); + let info = await I_ERC20DividendCheckpoint.getCheckpointData.call(checkpointID); + + assert.equal(info[0][0], account_investor1, "account match"); + assert.equal(info[0][1], account_investor2, "account match"); + assert.equal(info[0][2], account_temp, "account match"); + assert.equal(info[0][3], account_investor3, "account match"); + assert.equal(info[1][0].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_investor1, checkpointID)).toNumber(), "balance match"); + assert.equal(info[1][1].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_investor2, checkpointID)).toNumber(), "balance match"); + assert.equal(info[1][2].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_temp, checkpointID)).toNumber(), "balance match"); + assert.equal(info[1][3].toNumber(), (await I_SecurityToken.balanceOfAt.call(account_investor3, checkpointID)).toNumber(), "balance match"); + assert.equal(info[2][0].toNumber(), 0, "withholding match"); + assert.equal(info[2][1].toNumber(), BigNumber(100 * 10 ** 16).toNumber(), "withholding match"); + assert.equal(info[2][2].toNumber(), BigNumber(20 * 10 ** 16).toNumber(), "withholding match"); + assert.equal(info[2][3].toNumber(), 0, "withholding match"); + assert.equal(tx.logs[0].args._checkpointId.toNumber(), checkpointID); }); it("should allow manager with permission to create dividend with exclusion", async () => { let maturity = latestTime() + duration.days(1); let expiry = latestTime() + duration.days(10); - let exclusions = [1]; + + let limit = await I_ERC20DividendCheckpoint.EXCLUDED_ADDRESS_LIMIT(); + limit = limit.toNumber(); + let exclusions = []; + exclusions.push(account_temp); + while (--limit) exclusions.push(limit); + console.log(exclusions.length); let tx = await I_ERC20DividendCheckpoint.createDividendWithExclusions( maturity, expiry, @@ -1073,6 +1214,10 @@ contract("ERC20DividendCheckpoint", accounts => { { from: account_manager } ); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 9); + console.log("Gas used w/ max exclusions - non-default: " + tx.receipt.gasUsed); + let info = await I_ERC20DividendCheckpoint.getDividendProgress.call(tx.logs[0].args._dividendIndex); + assert.equal(info[0][2], account_temp, "account_temp is excluded"); + assert.equal(info[2][2], true, "account_temp is excluded"); }); it("should allow manager with permission to create dividend with checkpoint and exclusion", async () => { @@ -1094,6 +1239,28 @@ contract("ERC20DividendCheckpoint", accounts => { assert.equal(tx.logs[0].args._checkpointId.toNumber(), 10); }); + it("Update maturity and expiry dates on dividend", async () => { + await catchRevert(I_ERC20DividendCheckpoint.updateDividendDates(7, 0, 1, {from: account_polymath})); + let tx = await I_ERC20DividendCheckpoint.updateDividendDates(7, 0, 1, {from: token_owner}); + let info = await I_ERC20DividendCheckpoint.getDividendData.call(7); + assert.equal(info[1].toNumber(), 0); + assert.equal(info[2].toNumber(), 1); + // Can now reclaim the dividend + await I_ERC20DividendCheckpoint.reclaimDividend(7, {from: token_owner}); + }); + + it("Reclaim ERC20 tokens from the dividend contract", async () => { + let currentDividendBalance = BigNumber(await I_PolyToken.balanceOf.call(I_ERC20DividendCheckpoint.address)); + let currentIssuerBalance = BigNumber(await I_PolyToken.balanceOf.call(token_owner)); + await catchRevert(I_ERC20DividendCheckpoint.reclaimERC20(I_PolyToken.address, {from: account_polymath})); + let tx = await I_ERC20DividendCheckpoint.reclaimERC20(I_PolyToken.address, {from: token_owner}); + assert.equal(await I_PolyToken.balanceOf.call(I_ERC20DividendCheckpoint.address), 0); + let newIssuerBalance = BigNumber(await I_PolyToken.balanceOf.call(token_owner)); + console.log("Reclaimed: " + currentDividendBalance.toNumber()); + assert.equal(newIssuerBalance.sub(currentIssuerBalance).toNumber(), currentDividendBalance.toNumber()); + }); + + it("should allow manager with permission to create checkpoint", async () => { let initCheckpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_ERC20DividendCheckpoint.createCheckpoint({ from: account_manager }); @@ -1105,7 +1272,7 @@ contract("ERC20DividendCheckpoint", accounts => { it("should get the exact details of the factory", async () => { assert.equal((await I_ERC20DividendCheckpointFactory.getSetupCost.call()).toNumber(), 0); assert.equal((await I_ERC20DividendCheckpointFactory.getTypes.call())[0], 4); - assert.equal(await I_ERC20DividendCheckpointFactory.version.call(), "1.0.0"); + assert.equal(await I_ERC20DividendCheckpointFactory.version.call(), "2.1.1"); assert.equal( web3.utils.toAscii(await I_ERC20DividendCheckpointFactory.getName.call()).replace(/\u0000/g, ""), "ERC20DividendCheckpoint", diff --git a/test/f_ether_dividends.js b/test/f_ether_dividends.js index 261a7fd4b..1f7646ce6 100644 --- a/test/f_ether_dividends.js +++ b/test/f_ether_dividends.js @@ -4,6 +4,7 @@ import takeSnapshot, { increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall } from "./helpers/encodeCall"; import { catchRevert } from "./helpers/exceptions"; import { setUpPolymathNetwork, deployEtherDividendAndVerifyed, deployGPMAndVerifyed } from "./helpers/createInstances"; +import { encodeModuleCall } from "./helpers/encodeCall"; const SecurityToken = artifacts.require("./SecurityToken.sol"); const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); @@ -19,6 +20,7 @@ contract("EtherDividendCheckpoint", accounts => { let account_polymath; let account_issuer; let token_owner; + let wallet; let account_investor1; let account_investor2; let account_investor3; @@ -68,6 +70,7 @@ contract("EtherDividendCheckpoint", accounts => { const transferManagerKey = 2; const stoKey = 3; const checkpointKey = 4; + const DividendParameters = ["address"]; // Initial fee for ticker registry and security token registry const initRegFee = web3.utils.toWei("250"); @@ -85,6 +88,7 @@ contract("EtherDividendCheckpoint", accounts => { account_investor4 = accounts[9]; account_manager = accounts[5]; account_temp = accounts[2]; + wallet = accounts[3]; // Step 1: Deploy the genral PM ecosystem let instances = await setUpPolymathNetwork(account_polymath, token_owner); @@ -156,8 +160,9 @@ contract("EtherDividendCheckpoint", accounts => { it("Should successfully attach the ERC20DividendCheckpoint with the security token", async () => { await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); await catchRevert( - I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) ); @@ -166,7 +171,8 @@ contract("EtherDividendCheckpoint", accounts => { it("Should successfully attach the EtherDividendCheckpoint with the security token", async () => { let snapId = await takeSnapshot(); await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); - const tx = await I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, "", web3.utils.toWei("500", "ether"), 0, { + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(P_EtherDividendCheckpointFactory.address, bytesDividend, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); assert.equal(tx.logs[3].args._types[0].toNumber(), checkpointKey, "EtherDividendCheckpoint doesn't get deployed"); @@ -180,7 +186,8 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Should successfully attach the EtherDividendCheckpoint with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_EtherDividendCheckpointFactory.address, "", 0, 0, { from: token_owner }); + let bytesDividend = encodeModuleCall(DividendParameters, [wallet]); + const tx = await I_SecurityToken.addModule(I_EtherDividendCheckpointFactory.address, bytesDividend, 0, 0, { from: token_owner }); assert.equal(tx.logs[2].args._types[0].toNumber(), checkpointKey, "EtherDividendCheckpoint doesn't get deployed"); assert.equal( web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), @@ -343,9 +350,9 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer reclaims withholding tax", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BigNumber(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.withdrawWithholding(0, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(wallet)); assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.2", "ether")); }); @@ -356,6 +363,10 @@ contract("EtherDividendCheckpoint", accounts => { assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0", "ether")); }); + it("Set withholding tax of 100% on investor 2", async () => { + await I_EtherDividendCheckpoint.setWithholdingFixed([account_investor2], BigNumber(100 * 10 ** 16), { from: token_owner }); + }); + it("Buy some tokens for account_temp (1 ETH)", async () => { // Add the Investor in to the whitelist @@ -479,22 +490,23 @@ contract("EtherDividendCheckpoint", accounts => { let investor1BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor1)); let investor2BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor2)); let investor3BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor3)); - await I_EtherDividendCheckpoint.pushDividendPayment(2, 0, 10, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_EtherDividendCheckpoint.pushDividendPayment(2, 0, 10, { from: token_owner }); let investor1BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor1)); let investor2BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor2)); let investor3BalanceAfter2 = new BigNumber(await web3.eth.getBalance(account_investor3)); assert.equal(investor1BalanceAfter2.sub(investor1BalanceAfter1).toNumber(), 0); - assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), web3.utils.toWei("2.4", "ether")); + assert.equal(investor2BalanceAfter2.sub(investor2BalanceAfter1).toNumber(), 0); assert.equal(investor3BalanceAfter2.sub(investor3BalanceAfter1).toNumber(), 0); //Check fully claimed assert.equal((await I_EtherDividendCheckpoint.dividends(2))[5].toNumber(), web3.utils.toWei("11", "ether")); }); it("Issuer withdraws new withholding tax", async () => { - let issuerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let issuerBalance = new BigNumber(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.withdrawWithholding(2, { from: token_owner, gasPrice: 0 }); - let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(token_owner)); - assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("0.6", "ether")); + let issuerBalanceAfter = new BigNumber(await web3.eth.getBalance(wallet)); + assert.equal(issuerBalanceAfter.sub(issuerBalance).toNumber(), web3.utils.toWei("3", "ether")); }); it("Investor 2 transfers 1 ETH of his token balance to investor 1", async () => { @@ -524,7 +536,7 @@ contract("EtherDividendCheckpoint", accounts => { ); }); - it("Create another new dividend with bad expirty - fails", async () => { + it("Create another new dividend with bad expiry - fails", async () => { let maturity = latestTime() - duration.days(5); let expiry = latestTime() - duration.days(2); await catchRevert( @@ -641,7 +653,7 @@ contract("EtherDividendCheckpoint", accounts => { assert.equal(dividendAmount1[0].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount1[1].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount2[0].toNumber(), web3.utils.toWei("2", "ether")); - assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("0.4", "ether")); + assert.equal(dividendAmount2[1].toNumber(), web3.utils.toWei("2", "ether")); assert.equal(dividendAmount3[0].toNumber(), web3.utils.toWei("7", "ether")); assert.equal(dividendAmount3[1].toNumber(), web3.utils.toWei("0", "ether")); assert.equal(dividendAmount_temp[0].toNumber(), web3.utils.toWei("1", "ether")); @@ -659,7 +671,7 @@ contract("EtherDividendCheckpoint", accounts => { let investor3BalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_investor3)); let tempBalanceAfter1 = new BigNumber(await web3.eth.getBalance(account_temp)); assert.equal(investor1BalanceAfter1.sub(investor1Balance).toNumber(), 0); - assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), web3.utils.toWei("1.6", "ether")); + assert.equal(investor2BalanceAfter1.sub(investor2Balance).toNumber(), 0); assert.equal(investor3BalanceAfter1.sub(investor3Balance).toNumber(), 0); assert.equal(tempBalanceAfter1.sub(tempBalance).toNumber(), 0); }); @@ -699,9 +711,9 @@ contract("EtherDividendCheckpoint", accounts => { }); it("Issuer is able to reclaim dividend after expiry", async () => { - let tokenOwnerBalance = new BigNumber(await web3.eth.getBalance(token_owner)); + let tokenOwnerBalance = new BigNumber(await web3.eth.getBalance(wallet)); await I_EtherDividendCheckpoint.reclaimDividend(3, { from: token_owner, gasPrice: 0 }); - let tokenOwnerAfter = new BigNumber(await web3.eth.getBalance(token_owner)); + let tokenOwnerAfter = new BigNumber(await web3.eth.getBalance(wallet)); assert.equal(tokenOwnerAfter.sub(tokenOwnerBalance).toNumber(), web3.utils.toWei("7", "ether")); }); @@ -762,7 +774,7 @@ contract("EtherDividendCheckpoint", accounts => { let tokenBalanceAfter = new BigNumber(await web3.eth.getBalance(I_PolyToken.address)); assert.equal(investor1BalanceAfter.sub(investor1BalanceBefore).toNumber(), web3.utils.toWei("1", "ether")); - assert.equal(investor2BalanceAfter.sub(investor2BalanceBefore).toNumber(), web3.utils.toWei("1.6", "ether")); + assert.equal(investor2BalanceAfter.sub(investor2BalanceBefore).toNumber(), 0); assert.equal(investor3BalanceAfter.sub(investor3BalanceBefore).toNumber(), web3.utils.toWei("7", "ether")); assert.equal(tempBalanceAfter.sub(tempBalanceBefore).toNumber(), web3.utils.toWei("1", "ether")); assert.equal(tokenBalanceAfter.sub(tokenBalanceBefore).toNumber(), web3.utils.toWei("0", "ether")); @@ -781,11 +793,6 @@ contract("EtherDividendCheckpoint", accounts => { assert.equal(index.length, 0); }); - it("Get the init data", async () => { - let tx = await I_EtherDividendCheckpoint.getInitFunction.call(); - assert.equal(web3.utils.toAscii(tx).replace(/\u0000/g, ""), 0); - }); - it("Should get the listed permissions", async () => { let tx = await I_EtherDividendCheckpoint.getPermissions.call(); assert.equal(tx.length, 2); @@ -851,7 +858,7 @@ contract("EtherDividendCheckpoint", accounts => { let expiry = latestTime() + duration.days(10); let exclusions = [1]; let checkpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); - await I_SecurityToken.createCheckpoint({ from: token_owner }); + await I_SecurityToken.createCheckpoint({ from: token_owner }); await catchRevert(I_EtherDividendCheckpoint.createDividendWithCheckpointAndExclusions( maturity, expiry, @@ -941,8 +948,31 @@ contract("EtherDividendCheckpoint", accounts => { { from: account_manager, value: web3.utils.toWei("12", "ether") } ); assert.equal(tx.logs[0].args._checkpointId.toNumber(), 12); + console.log(tx.logs[0].args._dividendIndex.toNumber()); + }); + + it("Update maturity and expiry dates on dividend", async () => { + await catchRevert(I_EtherDividendCheckpoint.updateDividendDates(8, 0, 1, {from: account_polymath})); + let tx = await I_EtherDividendCheckpoint.updateDividendDates(8, 0, 1, {from: token_owner}); + let info = await I_EtherDividendCheckpoint.getDividendData.call(8); + assert.equal(info[1].toNumber(), 0); + assert.equal(info[2].toNumber(), 1); + // Can now reclaim the dividend + await I_EtherDividendCheckpoint.reclaimDividend(8, {from: token_owner}); + }); + + it("Reclaim ETH from the dividend contract", async () => { + let currentDividendBalance = BigNumber(await web3.eth.getBalance(I_EtherDividendCheckpoint.address)); + let currentIssuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + await catchRevert(I_EtherDividendCheckpoint.reclaimETH({from: account_polymath, gasPrice: 0})); + let tx = await I_EtherDividendCheckpoint.reclaimETH({from: token_owner, gasPrice: 0}); + assert.equal(await web3.eth.getBalance(I_EtherDividendCheckpoint.address), 0); + let newIssuerBalance = BigNumber(await web3.eth.getBalance(token_owner)); + console.log("Reclaimed: " + currentDividendBalance.toNumber()); + assert.equal(newIssuerBalance.sub(currentIssuerBalance).toNumber(), currentDividendBalance.toNumber()); }); + it("should allow manager with permission to create checkpoint", async () => { let initCheckpointID = await I_SecurityToken.createCheckpoint.call({ from: token_owner }); await I_EtherDividendCheckpoint.createCheckpoint({ from: account_manager }); @@ -954,7 +984,7 @@ contract("EtherDividendCheckpoint", accounts => { it("should get the exact details of the factory", async () => { assert.equal((await I_EtherDividendCheckpointFactory.getSetupCost.call()).toNumber(), 0); assert.equal((await I_EtherDividendCheckpointFactory.getTypes.call())[0], 4); - assert.equal(await I_EtherDividendCheckpointFactory.version.call(), "1.0.0"); + assert.equal(await I_EtherDividendCheckpointFactory.version.call(), "2.1.1"); assert.equal( web3.utils.toAscii(await I_EtherDividendCheckpointFactory.getName.call()).replace(/\u0000/g, ""), "EtherDividendCheckpoint", diff --git a/test/h_general_transfer_manager.js b/test/h_general_transfer_manager.js index 3e060c288..76a9d5806 100644 --- a/test/h_general_transfer_manager.js +++ b/test/h_general_transfer_manager.js @@ -92,6 +92,7 @@ contract("GeneralTransferManager", accounts => { account_investor1 = accounts[8]; account_investor2 = accounts[9]; account_delegate = accounts[7]; + account_investor3 = accounts[5]; account_investor4 = accounts[6]; account_affiliates1 = accounts[3]; @@ -172,14 +173,14 @@ contract("GeneralTransferManager", accounts => { await catchRevert( I_SecurityToken.addModule(P_GeneralTransferManagerFactory.address, "", web3.utils.toWei("500"), 0, {from: account_issuer}) ); - }) + }); it("Should attach the paid GTM", async() => { let snap_id = await takeSnapshot(); await I_PolyToken.getTokens(web3.utils.toWei("500"), I_SecurityToken.address); await I_SecurityToken.addModule(P_GeneralTransferManagerFactory.address, "", web3.utils.toWei("500"), 0, {from: account_issuer}); await revertToSnapshot(snap_id); - }) + }); it("Should whitelist the affiliates before the STO attached", async () => { let tx = await I_GeneralTransferManager.modifyWhitelistMulti( @@ -195,6 +196,32 @@ contract("GeneralTransferManager", accounts => { ); assert.equal(tx.logs[0].args._investor, account_affiliates1); assert.equal(tx.logs[1].args._investor, account_affiliates2); + assert.deepEqual(await I_GeneralTransferManager.getInvestors.call(), [account_affiliates1, account_affiliates2]); + console.log(await I_GeneralTransferManager.getAllInvestorsData.call()); + console.log(await I_GeneralTransferManager.getInvestorsData.call([account_affiliates1, account_affiliates2])); + }); + + it("Should whitelist lots of addresses and check gas", async () => { + let mockInvestors = []; + for (let i = 0; i < 50; i++) { + mockInvestors.push("0x1000000000000000000000000000000000000000".substring(0,42-i.toString().length) + i.toString()); + } + + let times = range1(50); + let bools = rangeB(50); + let tx = await I_GeneralTransferManager.modifyWhitelistMulti( + mockInvestors, + times, + times, + times, + bools, + { + from: account_issuer, + gas: 7900000 + } + ); + console.log("Multi Whitelist x 50: " + tx.receipt.gasUsed); + assert.deepEqual(await I_GeneralTransferManager.getInvestors.call(), [account_affiliates1, account_affiliates2].concat(mockInvestors)); }); it("Should mint the tokens to the affiliates", async () => { @@ -320,7 +347,7 @@ contract("GeneralTransferManager", accounts => { it("Should fail in buying the tokens from the STO -- because amount is 0", async() => { await catchRevert( I_DummySTO.generateTokens(account_investor1, 0, { from: token_owner }) - ); + ); }); it("Should fail in buying the tokens from the STO -- because STO is paused", async() => { @@ -342,6 +369,90 @@ contract("GeneralTransferManager", accounts => { }); }); + describe("Buy tokens using on-chain whitelist and defaults", async () => { + // let snap_id; + + it("Should Buy the tokens", async () => { + // Add the Investor in to the whitelist + // snap_id = await takeSnapshot(); + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + 0, + 0, + latestTime() + duration.days(20), + true, + { + from: account_issuer, + gas: 6000000 + } + ); + + assert.equal( + tx.logs[0].args._investor.toLowerCase(), + account_investor1.toLowerCase(), + "Failed in adding the investor in whitelist" + ); + + tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(20), + true, + { + from: account_issuer, + gas: 6000000 + } + ); + + assert.equal( + tx.logs[0].args._investor.toLowerCase(), + account_investor2.toLowerCase(), + "Failed in adding the investor in whitelist" + ); + + // Jump time + await increaseTime(5000); + + // Can transfer tokens + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1}); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("1", "ether")); + }); + + it("Add a from default and check transfers are disabled then enabled in the future", async () => { + let tx = await I_GeneralTransferManager.changeDefaults(latestTime() + duration.days(5), 0, {from: token_owner}); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), {from: account_investor2}); + await catchRevert(I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1})); + await increaseTime(duration.days(5)); + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1}); + }); + + it("Add a to default and check transfers are disabled then enabled in the future", async () => { + let tx = await I_GeneralTransferManager.changeDefaults(0, latestTime() + duration.days(5), {from: token_owner}); + await catchRevert(I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), {from: account_investor2})); + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), {from: account_investor1}); + await increaseTime(duration.days(5)); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("2", "ether"), {from: account_investor2}); + // revert changes + await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + 0, + 0, + 0, + false, + { + from: account_issuer, + gas: 6000000 + } + ); + await I_GeneralTransferManager.changeDefaults(0, 0, {from: token_owner}); + }); + + + + }); + describe("Buy tokens using off-chain whitelist", async () => { it("Should buy the tokens -- Failed due to investor is not in the whitelist", async () => { await catchRevert(I_DummySTO.generateTokens(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner })); @@ -690,7 +801,7 @@ contract("GeneralTransferManager", accounts => { [fromTime], [toTime, toTime], [expiryTime, expiryTime], - [true, true], + [1, 1], { from: account_delegate, gas: 6000000 @@ -779,7 +890,7 @@ contract("GeneralTransferManager", accounts => { "Allows an issuer to maintain a time based whitelist of authorised token holders.Addresses are added via modifyWhitelist and take a fromTime (the time from which they can send tokens) and a toTime (the time from which they can receive tokens). There are additional flags, allowAllWhitelistIssuances, allowAllWhitelistTransfers & allowAllTransfers which allow you to set corresponding contract level behaviour. Init function takes no parameters.", "Wrong Module added" ); - assert.equal(await I_GeneralPermissionManagerFactory.version.call(), "1.0.0"); + assert.equal(await I_GeneralTransferManagerFactory.version.call(), "2.1.0"); }); it("Should get the tags of the factory", async () => { @@ -836,3 +947,6 @@ contract("GeneralTransferManager", accounts => { }) }); }); + +function range1(i) {return i?range1(i-1).concat(i):[]} +function rangeB(i) {return i?rangeB(i-1).concat(0):[]} diff --git a/test/helpers/createInstances.js b/test/helpers/createInstances.js index 2021d350e..4335e52b3 100644 --- a/test/helpers/createInstances.js +++ b/test/helpers/createInstances.js @@ -8,28 +8,35 @@ const CappedSTOFactory = artifacts.require("./CappedSTOFactory.sol"); const SecurityTokenRegistryProxy = artifacts.require("./SecurityTokenRegistryProxy.sol"); const SecurityTokenRegistry = artifacts.require("./SecurityTokenRegistry.sol"); const SecurityTokenRegistryMock = artifacts.require("./SecurityTokenRegistryMock.sol"); +const ERC20DividendCheckpoint = artifacts.require("./ERC20DividendCheckpoint.sol"); +const EtherDividendCheckpoint = artifacts.require("./EtherDividendCheckpoint.sol"); const ERC20DividendCheckpointFactory = artifacts.require("./ERC20DividendCheckpointFactory.sol"); const EtherDividendCheckpointFactory = artifacts.require("./EtherDividendCheckpointFactory.sol"); const ManualApprovalTransferManagerFactory = artifacts.require("./ManualApprovalTransferManagerFactory.sol"); -const SingleTradeVolumeRestrictionManagerFactory = artifacts.require('./SingleTradeVolumeRestrictionTMFactory.sol'); const TrackedRedemptionFactory = artifacts.require("./TrackedRedemptionFactory.sol"); const PercentageTransferManagerFactory = artifacts.require("./PercentageTransferManagerFactory.sol"); +const BlacklistTransferManagerFactory = artifacts.require("./BlacklistTransferManagerFactory.sol"); const ScheduledCheckpointFactory = artifacts.require('./ScheduledCheckpointFactory.sol'); const USDTieredSTOFactory = artifacts.require("./USDTieredSTOFactory.sol"); -const USDTieredSTOProxyFactory = artifacts.require("./USDTieredSTOProxyFactory"); +const USDTieredSTO = artifacts.require("./USDTieredSTO"); const ManualApprovalTransferManager = artifacts.require("./ManualApprovalTransferManager"); const FeatureRegistry = artifacts.require("./FeatureRegistry.sol"); const STFactory = artifacts.require("./STFactory.sol"); +const GeneralTransferManager = artifacts.require("./GeneralTransferManager.sol"); const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); const CountTransferManagerFactory = artifacts.require("./CountTransferManagerFactory.sol"); -const VolumeRestrictionTransferManagerFactory = artifacts.require("./LockupVolumeRestrictionTMFactory"); +const LockUpTransferManagerFactory = artifacts.require("./LockUpTransferManagerFactory"); const PreSaleSTOFactory = artifacts.require("./PreSaleSTOFactory.sol"); const PolyToken = artifacts.require("./PolyToken.sol"); const PolyTokenFaucet = artifacts.require("./PolyTokenFaucet.sol"); 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"); const Web3 = require("web3"); const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); // Hardcoded development port @@ -41,16 +48,21 @@ let I_TrackedRedemptionFactory; let I_ScheduledCheckpointFactory; let I_MockBurnFactory; let I_MockWrongTypeBurnFactory; -let I_SingleTradeVolumeRestrictionManagerFactory; let I_ManualApprovalTransferManagerFactory; let I_VolumeRestrictionTransferManagerFactory; let I_PercentageTransferManagerFactory; +let I_EtherDividendCheckpointLogic; 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; @@ -61,8 +73,11 @@ let I_SecurityToken; let I_DummySTOFactory; let I_PolyToken; let I_STFactory; +let I_USDTieredSTOLogic; let I_PolymathRegistry; let I_SecurityTokenRegistryProxy; +let I_BlacklistTransferManagerFactory; +let I_VestingEscrowWalletLogic; let I_STRProxied; let I_MRProxied; @@ -82,7 +97,9 @@ export async function setUpPolymathNetwork(account_polymath, token_owner) { let b = await deployFeatureRegistry(account_polymath); // STEP 3: Deploy the ModuleRegistry let c = await deployModuleRegistry(account_polymath); - // STEP 4: Deploy the GeneralTransferManagerFactory + // STEP 4a: Deploy the GeneralTransferManagerFactory + let logic = await deployGTMLogic(account_polymath); + // STEP 4b: Deploy the GeneralTransferManagerFactory let d = await deployGTM(account_polymath); // Step 6: Deploy the STversionProxy contract let e = await deploySTFactory(account_polymath); @@ -97,7 +114,7 @@ export async function setUpPolymathNetwork(account_polymath, token_owner) { } -async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) { +export async function deployPolyRegistryAndPolyToken(account_polymath, token_owner) { // Step 0: Deploy the PolymathRegistry I_PolymathRegistry = await PolymathRegistry.new({ from: account_polymath }); @@ -127,8 +144,20 @@ async function deployModuleRegistry(account_polymath) { return new Array(I_ModuleRegistry, I_ModuleRegistryProxy, I_MRProxied); } +async function deployGTMLogic(account_polymath) { + I_GeneralTransferManagerLogic = await GeneralTransferManager.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + + assert.notEqual( + I_GeneralTransferManagerLogic.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "GeneralTransferManagerLogic contract was not deployed" + ); + + return new Array(I_GeneralTransferManagerLogic); +} + async function deployGTM(account_polymath) { - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, { from: account_polymath }); + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, I_GeneralTransferManagerLogic.address, { from: account_polymath }); assert.notEqual( I_GeneralTransferManagerFactory.address.valueOf(), @@ -193,10 +222,10 @@ async function registerAndVerifyByMR(factoryAdrress, owner, mr) { /// Deploy the TransferManagers export async function deployGTMAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); + 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" ); @@ -206,6 +235,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 }); @@ -243,28 +288,28 @@ export async function deployPercentageTMAndVerified(accountPolymath, MRProxyInst return new Array(I_PercentageTransferManagerFactory); } -export async function deployLockupVolumeRTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_VolumeRestrictionTransferManagerFactory = await VolumeRestrictionTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployBlacklistTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { + I_BlacklistTransferManagerFactory = await BlacklistTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); assert.notEqual( - I_VolumeRestrictionTransferManagerFactory.address.valueOf(), + I_BlacklistTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", - "VolumeRestrictionTransferManagerFactory contract was not deployed" + "BlacklistTransferManagerFactory contract was not deployed" ); - await registerAndVerifyByMR(I_VolumeRestrictionTransferManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_VolumeRestrictionTransferManagerFactory); + await registerAndVerifyByMR(I_BlacklistTransferManagerFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_BlacklistTransferManagerFactory); } -export async function deploySingleTradeVolumeRMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_SingleTradeVolumeRestrictionManagerFactory = await SingleTradeVolumeRestrictionManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); +export async function deployLockupVolumeRTMAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { + I_VolumeRestrictionTransferManagerFactory = await LockUpTransferManagerFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); assert.notEqual( - I_SingleTradeVolumeRestrictionManagerFactory.address.valueOf(), + I_VolumeRestrictionTransferManagerFactory.address.valueOf(), "0x0000000000000000000000000000000000000000", - "SingleTradeVolumeRestrictionManagerFactory contract was not deployed" + "LockUpTransferManagerFactory contract was not deployed" ); - await registerAndVerifyByMR(I_SingleTradeVolumeRestrictionManagerFactory.address, accountPolymath, MRProxyInstance); - return new Array(I_SingleTradeVolumeRestrictionManagerFactory); + await registerAndVerifyByMR(I_VolumeRestrictionTransferManagerFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_VolumeRestrictionTransferManagerFactory); } export async function deployScheduleCheckpointAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { @@ -337,9 +382,9 @@ export async function deployPresaleSTOAndVerified(accountPolymath, MRProxyInstan } export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_USDTieredSTOProxyFactory = await USDTieredSTOProxyFactory.new({from: accountPolymath}); + I_USDTieredSTOLogic = await USDTieredSTO.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); - I_USDTieredSTOFactory = await USDTieredSTOFactory.new(polyToken, setupCost, 0, 0, I_USDTieredSTOProxyFactory.address, { from: accountPolymath }); + I_USDTieredSTOFactory = await USDTieredSTOFactory.new(polyToken, setupCost, 0, 0, I_USDTieredSTOLogic.address, { from: accountPolymath }); assert.notEqual( I_USDTieredSTOFactory.address.valueOf(), @@ -355,7 +400,8 @@ export async function deployUSDTieredSTOAndVerified(accountPolymath, MRProxyInst /// Deploy the Dividend Modules export async function deployERC20DividendAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_ERC20DividendCheckpointFactory = await ERC20DividendCheckpointFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); + I_ERC20DividendCheckpointLogic = await ERC20DividendCheckpoint.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_ERC20DividendCheckpointFactory = await ERC20DividendCheckpointFactory.new(polyToken, setupCost, 0, 0, I_ERC20DividendCheckpointLogic.address, { from: accountPolymath }); assert.notEqual( I_ERC20DividendCheckpointFactory.address.valueOf(), @@ -367,7 +413,8 @@ export async function deployERC20DividendAndVerifyed(accountPolymath, MRProxyIns } export async function deployEtherDividendAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { - I_EtherDividendCheckpointFactory = await EtherDividendCheckpointFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); + I_EtherDividendCheckpointLogic = await EtherDividendCheckpoint.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_EtherDividendCheckpointFactory = await EtherDividendCheckpointFactory.new(polyToken, setupCost, 0, 0, I_EtherDividendCheckpointLogic.address, { from: accountPolymath }); assert.notEqual( I_EtherDividendCheckpointFactory.address.valueOf(), @@ -395,6 +442,19 @@ export async function deployRedemptionAndVerifyed(accountPolymath, MRProxyInstan return new Array(I_TrackedRedemptionFactory); } +export async function deployVestingEscrowWalletAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { + I_VestingEscrowWalletLogic = await VestingEscrowWallet.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: accountPolymath }); + I_VestingEscrowWalletFactory = await VestingEscrowWalletFactory.new(polyToken, setupCost, 0, 0, I_VestingEscrowWalletLogic.address, { from: accountPolymath }); + + assert.notEqual( + I_VestingEscrowWalletFactory.address.valueOf(), + "0x0000000000000000000000000000000000000000", + "VestingEscrowWalletFactory contract was not deployed" + ); + + await registerAndVerifyByMR(I_VestingEscrowWalletFactory.address, accountPolymath, MRProxyInstance); + return new Array(I_VestingEscrowWalletFactory); +} export async function deployMockRedemptionAndVerifyed(accountPolymath, MRProxyInstance, polyToken, setupCost) { I_MockBurnFactory = await MockBurnFactory.new(polyToken, setupCost, 0, 0, { from: accountPolymath }); @@ -422,8 +482,6 @@ export async function deployMockWrongTypeRedemptionAndVerifyed(accountPolymath, return new Array(I_MockWrongTypeBurnFactory); } - - /// Helper function function mergeReturn(returnData) { let returnArray = new Array(); diff --git a/test/helpers/exceptions.js b/test/helpers/exceptions.js index 22c05be07..ea0327af8 100644 --- a/test/helpers/exceptions.js +++ b/test/helpers/exceptions.js @@ -25,6 +25,9 @@ module.exports = { catchRevert: async function(promise) { await tryCatch(promise, "revert"); }, + catchPermission: async function(promise) { + await tryCatch(promise, "revert Permission check failed"); + }, catchOutOfGas: async function(promise) { await tryCatch(promise, "out of gas"); }, diff --git a/test/i_Issuance.js b/test/i_Issuance.js index ac1fcdd37..5cef65baf 100644 --- a/test/i_Issuance.js +++ b/test/i_Issuance.js @@ -72,7 +72,7 @@ contract("Issuance", accounts => { //let startTime; // Start time will be 5000 seconds more than the latest time //let endTime; // Add 30 days more const cap = web3.utils.toWei("10000"); - const rate = 1000; + const rate = web3.utils.toWei("1000"); const fundRaiseType = [0]; const cappedSTOSetupCost = web3.utils.toWei("20000", "ether"); const maxCost = cappedSTOSetupCost; diff --git a/test/j_manual_approval_transfer_manager.js b/test/j_manual_approval_transfer_manager.js index 5779a1c13..985d471ca 100644 --- a/test/j_manual_approval_transfer_manager.js +++ b/test/j_manual_approval_transfer_manager.js @@ -68,6 +68,9 @@ contract("ManualApprovalTransferManager", accounts => { const transferManagerKey = 2; const stoKey = 3; + let expiryTimeMA; + let approvalTime; + // Initial fee for ticker registry and security token registry const initRegFee = web3.utils.toWei("250"); @@ -170,7 +173,7 @@ contract("ManualApprovalTransferManager", accounts => { account_investor1, latestTime(), latestTime(), - latestTime() + duration.days(10), + latestTime() + duration.days(30), true, { from: account_issuer, @@ -188,9 +191,9 @@ contract("ManualApprovalTransferManager", accounts => { await increaseTime(5000); // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei("4", "ether"), { from: token_owner }); + await I_SecurityToken.mint(account_investor1, web3.utils.toWei("30", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("4", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("30", "ether")); }); it("Should Buy some more tokens", async () => { @@ -200,7 +203,7 @@ contract("ManualApprovalTransferManager", accounts => { account_investor2, latestTime(), latestTime(), - latestTime() + duration.days(10), + latestTime() + duration.days(30), true, { from: account_issuer, @@ -215,9 +218,9 @@ contract("ManualApprovalTransferManager", accounts => { ); // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.mint(account_investor2, web3.utils.toWei("10", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("10", "ether")); }); it("Should successfully attach the ManualApprovalTransferManager with the security token", async () => { @@ -306,9 +309,9 @@ contract("ManualApprovalTransferManager", accounts => { await I_ManualApprovalTransferManager.pause({from: token_owner}); // Add the Investor in to the whitelist // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei("1", "ether"), { from: token_owner }); + await I_SecurityToken.mint(account_investor3, web3.utils.toWei("10", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("1", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei("10", "ether")); // Unpause at the transferManager level await I_ManualApprovalTransferManager.unpause({from: token_owner}); }); @@ -318,7 +321,7 @@ contract("ManualApprovalTransferManager", accounts => { // Mint some tokens await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1", "ether"), { from: account_investor2 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("5", "ether")); + assert.equal((await I_SecurityToken.balanceOf(account_investor1)).toNumber(), web3.utils.toWei("31", "ether")); }); it("Should fail to add a manual approval because invalid _to address", async () => { @@ -328,6 +331,7 @@ contract("ManualApprovalTransferManager", accounts => { "", web3.utils.toWei("2", "ether"), latestTime() + duration.days(1), + "DESCRIPTION", { from: token_owner } ) ); @@ -340,156 +344,424 @@ contract("ManualApprovalTransferManager", accounts => { account_investor4, web3.utils.toWei("2", "ether"), 99999, + "DESCRIPTION", { from: token_owner } ) ); }); - it("Add a manual approval for a 4th investor", async () => { + it("Add a manual approval for a 4th investor & return correct length", async () => { + approvalTime = latestTime() + duration.days(1); await I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - { from: token_owner } + web3.utils.toWei("3", "ether"), + approvalTime, + "DESCRIPTION", + { + from: token_owner + } ); + assert.equal((await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toNumber(), 1); }); - it("Add a manual approval for a 5th investor from issuance", async () => { - await I_ManualApprovalTransferManager.addManualApproval( - "", - account_investor5, - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - { from: token_owner } - ); - }); + it("Should return all approvals correctly", async () => { - it("Should fail to add a manual approval because allowance is laready exists", async () => { + console.log("current approval length is " + (await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toNumber()); + + let tx = await I_ManualApprovalTransferManager.getAllApprovals({from: token_owner }); + assert.equal(tx[0][0], account_investor1); + console.log("1"); + assert.equal(tx[1][0], account_investor4); + console.log("2"); + assert.equal(tx[2][0], web3.utils.toWei("3")); + console.log("3"); + assert.equal(tx[3][0].toNumber(), approvalTime); + console.log("4"); + assert.equal(web3.utils.toUtf8(tx[4][0]), "DESCRIPTION"); + }) + + it("Should try to add the same manual approval for the same `_from` & `_to` address", async() => { await catchRevert( I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, - web3.utils.toWei("2", "ether"), - latestTime() + duration.days(5), - { from: token_owner } + web3.utils.toWei("5", "ether"), + latestTime() + duration.days(1), + "DESCRIPTION", + { + from: token_owner + } ) ); - }); - - it("Should fail to revoke manual approval because invalid _to address", async () => { - await catchRevert(I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, "", { from: token_owner })); - }); + }) - it("Should revoke manual approval", async () => { - let tx = await I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, { - from: token_owner - }); - assert.equal(tx.logs[0].args._from, account_investor1); - assert.equal(tx.logs[0].args._to, account_investor4); - assert.equal(tx.logs[0].args._addedBy, token_owner); - await I_ManualApprovalTransferManager.addManualApproval( + it("Check verifyTransfer without actually transferring", async () => { + let verified = await I_SecurityToken.verifyTransfer.call( account_investor1, account_investor4, web3.utils.toWei("2", "ether"), - latestTime() + duration.days(1), - { from: token_owner } + "" ); - }); + console.log(JSON.stringify(verified)); + assert.equal(verified, true); - it("Use 50% of manual approval for transfer", async () => { - await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1", "ether"), { from: account_investor1 }); + verified = await I_SecurityToken.verifyTransfer.call(account_investor1, account_investor4, web3.utils.toWei("4", "ether"), ""); + assert.equal(verified, false); - assert.equal((await I_SecurityToken.balanceOf(account_investor4)).toNumber(), web3.utils.toWei("1", "ether")); + verified = await I_SecurityToken.verifyTransfer.call(account_investor1, account_investor4, web3.utils.toWei("1", "ether"), ""); + assert.equal(verified, true); }); + it("Should fail to sell the tokens more than the allowance", async() => { + await catchRevert( + I_SecurityToken.transfer(account_investor4, web3.utils.toWei("4"), {from: account_investor1}) + ); + }) + it("Approval fails with wrong from to address", async () => { - await catchRevert(I_SecurityToken.transfer(account_investor5, web3.utils.toWei("1", "ether"), { from: account_investor1 })); + await catchRevert(I_SecurityToken.transfer(account_investor5, web3.utils.toWei("1", "ether"), { from: account_investor1 })); }); - it("Use 100% of issuance approval", async () => { - await I_SecurityToken.mint(account_investor5, web3.utils.toWei("2", "ether"), { from: token_owner }); - assert.equal((await I_SecurityToken.balanceOf(account_investor5)).toNumber(), web3.utils.toWei("2", "ether")); + it("Should sell the tokens to investor 4 (GTM will give INVALID as investor 4 not in the whitelist)", async() => { + let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); + let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); }); - it("Check verifyTransfer without actually transferring", async () => { - let verified = await I_SecurityToken.verifyTransfer.call( + it("Should sell more tokens to investor 4 with in the same day(GTM will give INVALID as investor 4 not in the whitelist)", async() => { + let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); + let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); + + + let tx = await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor1); + let tx2 = await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor4); + + assert.equal(tx2[0].length, 1); + assert.equal(tx[0].length, 1); + }); + + it("Should fail to transact after the approval get expired", async() => { + await increaseTime(duration.days(1)); + await catchRevert( + I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}) + ); + }); + + it("Should fail to modify the manual approval when the approval get expired", async() => { + await catchRevert( + I_ManualApprovalTransferManager.modifyManualApproval( + account_investor1, + account_investor4, + latestTime() + duration.days(2), + web3.utils.toWei("5"), + "New Description", + false, + { + from: token_owner + } + ) + ); + }); + + it("Should attach the manual approval for the investor4 again", async() => { + assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor4))[0].length, 0); + await I_ManualApprovalTransferManager.addManualApproval( account_investor1, account_investor4, - web3.utils.toWei("1", "ether"), - "" + web3.utils.toWei("2", "ether"), + latestTime() + duration.days(1), + "DESCRIPTION", + { + from: token_owner + } ); - console.log(JSON.stringify(verified)); - assert.equal(verified, true); + assert.equal((await I_ManualApprovalTransferManager.getTotalApprovalsLength.call()).toNumber(), 1); + let data = await I_ManualApprovalTransferManager.approvals.call(0); + assert.equal(data[0], account_investor1); + assert.equal(data[1], account_investor4); + assert.equal(data[2], web3.utils.toWei("2")); + assert.equal(web3.utils.toUtf8(data[4]), "DESCRIPTION"); + }); - verified = await I_SecurityToken.verifyTransfer.call(account_investor1, account_investor4, web3.utils.toWei("2", "ether"), ""); - assert.equal(verified, false); + it("Should modify the manual approval expiry time for 4th investor", async () => { + expiryTimeMA = latestTime() + duration.days(3); + let tx = await I_ManualApprovalTransferManager.modifyManualApproval( + account_investor1, + account_investor4, + expiryTimeMA, + 0, + "New Description", + true, + { + from: token_owner + } + ); + + let data = await I_ManualApprovalTransferManager.approvals.call(0); + assert.equal(data[0], account_investor1); + assert.equal(data[1], account_investor4); + assert.equal(data[2], web3.utils.toWei("2")); + assert.equal(data[3].toNumber(), expiryTimeMA); + assert.equal(web3.utils.toUtf8(data[4]), "New Description"); + assert.equal(tx.logs[0].args._from, account_investor1); + assert.equal(tx.logs[0].args._to, account_investor4); + assert.equal((tx.logs[0].args._expiryTime).toNumber(), expiryTimeMA); + assert.equal((tx.logs[0].args._allowance).toNumber(), web3.utils.toWei("2")); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._description), "New Description"); + }); - verified = await I_SecurityToken.verifyTransfer.call(account_investor1, account_investor4, web3.utils.toWei("1", "ether"), ""); - assert.equal(verified, true); + it("Should transact after two days", async() => { + await increaseTime(2); + let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); + let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); }); - it("Use remaining 50% of manual approval for transfer", async () => { - await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1", "ether"), { from: account_investor1 }); + it("Should modify the allowance of the manual approval (increase)", async() => { + await I_ManualApprovalTransferManager.modifyManualApproval( + account_investor1, + account_investor4, + expiryTimeMA, + web3.utils.toWei("4"), + "New Description", + true, + { + from: token_owner + } + ); - assert.equal((await I_SecurityToken.balanceOf(account_investor4)).toNumber(), web3.utils.toWei("2", "ether")); + let data = await I_ManualApprovalTransferManager.approvals.call(0); + assert.equal(data[0], account_investor1); + assert.equal(data[1], account_investor4); + assert.equal(data[2].toNumber(), web3.utils.toWei("5")); + assert.equal(data[3].toNumber(), expiryTimeMA); + assert.equal(web3.utils.toUtf8(data[4]), "New Description"); }); - it("Check further transfers fail", async () => { - await catchRevert(I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1", "ether"), { from: account_investor1 })); - - //Check that other transfers are still valid - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 }); + it("Should transact according to new allowance", async() => { + let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("3"), {from: account_investor1}); + let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 3); }); - it("Should fail to add a manual block because invalid _to address", async () => { - await catchRevert( - I_ManualApprovalTransferManager.addManualBlocking(account_investor1, "", latestTime() + duration.days(1), { + it("Should decrease the allowance", async() => { + await I_ManualApprovalTransferManager.modifyManualApproval( + account_investor1, + account_investor4, + expiryTimeMA, + web3.utils.toWei("1"), + "New Description", + false, + { from: token_owner - }) + } ); + + let data = await I_ManualApprovalTransferManager.approvals.call(0); + assert.equal(data[0], account_investor1); + assert.equal(data[1], account_investor4); + assert.equal(data[2].toNumber(), web3.utils.toWei("1")); + assert.equal(data[3].toNumber(), expiryTimeMA); + assert.equal(web3.utils.toUtf8(data[4]), "New Description"); }); - it("Should fail to add a manual block because invalid expiry time", async () => { + it("Should fail to transfer the tokens because allowance get changed", async() => { await catchRevert( - I_ManualApprovalTransferManager.addManualBlocking(account_investor1, account_investor2, 99999, { from: token_owner }) + I_SecurityToken.transfer(account_investor4, web3.utils.toWei("2"), {from: account_investor1}) ); }); - it("Add a manual block for a 2nd investor", async () => { - await I_ManualApprovalTransferManager.addManualBlocking(account_investor1, account_investor2, latestTime() + duration.days(1), { - from: token_owner - }); + it("Should successfully transfer the tokens within the allowance limit", async() => { + let oldBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei("1"), {from: account_investor1}); + let newBal4 = await I_SecurityToken.balanceOf.call(account_investor4); + assert.equal((newBal4.minus(oldBal4)).dividedBy(new BigNumber(10).pow(18)).toNumber(), 1); }); - it("Should fail to add a manual block because blocking already exist", async () => { + it("Should fail to modify because allowance is zero", async() => { await catchRevert( - I_ManualApprovalTransferManager.addManualBlocking(account_investor1, account_investor2, latestTime() + duration.days(5), { from: token_owner }) + I_ManualApprovalTransferManager.modifyManualApproval( + account_investor1, + account_investor4, + expiryTimeMA, + web3.utils.toWei("5"), + "New Description", + false, + { + from: token_owner + } + ) ); }); - it("Check manual block causes failure", async () => { - await catchRevert(I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 })); + it("Should fail to revoke the manual Approval -- bad owner", async() => { + await catchRevert( + I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, {from: account_investor5}) + ); + }) + + it("Should revoke the manual Approval b/w investor4 and 1", async() => { + await I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, {from: token_owner}); + assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor1))[0].length, 0); + assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor4))[0].length, 0); }); - it("Should fail to revoke manual block because invalid _to address", async () => { - await catchRevert(I_ManualApprovalTransferManager.revokeManualBlocking(account_investor1, "0x0", { from: token_owner })); + it("Should fail to revoke the same manual approval again", async() => { + await catchRevert( + I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, {from: token_owner}) + ); }); - it("Revoke manual block and check transfer works", async () => { - await I_ManualApprovalTransferManager.revokeManualBlocking(account_investor1, account_investor2, { from: token_owner }); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei("2", "ether")); + it("Should fail to add multiple manual approvals -- failed because of bad owner", async () => { + await catchRevert ( + I_ManualApprovalTransferManager.addManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], + [latestTime() + duration.days(1),latestTime() + duration.days(1)], + ["DESCRIPTION_1", "DESCRIPTION_2"], + { + from: account_investor5 + } + ) + ) + }); + + it("Should fail to add multiple manual approvals -- failed because of length mismatch", async () => { + await catchRevert ( + I_ManualApprovalTransferManager.addManualApprovalMulti( + [account_investor2], + [account_investor3,account_investor4], + [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], + [latestTime() + duration.days(1),latestTime() + duration.days(1)], + ["DESCRIPTION_1", "DESCRIPTION_2"], + { + from: token_owner + } + ) + ) + }); + + it("Should fail to add multiple manual approvals -- failed because of length mismatch", async () => { + await catchRevert ( + I_ManualApprovalTransferManager.addManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], + [latestTime() + duration.days(1)], + ["DESCRIPTION_1", "DESCRIPTION_2"], + { + from: token_owner + } + ) + ) + }); + + it("Should fail to add multiple manual approvals -- failed because of length mismatch", async () => { + await catchRevert ( + I_ManualApprovalTransferManager.addManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + [web3.utils.toWei("2", "ether")], + [latestTime() + duration.days(1),latestTime() + duration.days(1)], + ["DESCRIPTION_1", "DESCRIPTION_2"], + { + from: token_owner + } + ) + ) + }); + + it("Should fail to add multiple manual approvals -- failed because of length mismatch", async () => { + await catchRevert ( + I_ManualApprovalTransferManager.addManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], + [latestTime() + duration.days(1),latestTime() + duration.days(1)], + ["DESCRIPTION_1"], + { + from: token_owner + } + ) + ) + }); + + it("Add multiple manual approvals", async () => { + let time = latestTime() + duration.days(1); + await I_ManualApprovalTransferManager.addManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + [web3.utils.toWei("2", "ether"), web3.utils.toWei("2", "ether")], + [time,latestTime() + duration.days(1)], + ["DESCRIPTION_1", "DESCRIPTION_2"], + { + from: token_owner + } + ); + + assert.equal(await I_ManualApprovalTransferManager.getTotalApprovalsLength.call(), 2); + assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor3))[0].length , 2); + assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor3))[0][1], account_investor3); + assert.equal((await I_ManualApprovalTransferManager.getActiveApprovalsToUser.call(account_investor3))[1][0], account_investor3); + let approvalDetail = await I_ManualApprovalTransferManager.getApprovalDetails.call(account_investor2, account_investor3); + assert.equal(approvalDetail[0].toNumber(), time); + assert.equal(approvalDetail[1].toNumber(), web3.utils.toWei("2", "ether")); + assert.equal(web3.utils.toUtf8(approvalDetail[2]), "DESCRIPTION_1"); }); - it("Check manual block ignored after expiry", async () => { - await I_ManualApprovalTransferManager.addManualBlocking(account_investor1, account_investor2, latestTime() + duration.days(1), { - from: token_owner - }); + it("Should fail to revoke the multiple manual approvals -- because of bad owner", async() => { + await catchRevert( + I_ManualApprovalTransferManager.revokeManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + { + from: account_investor5 + } + ) + ); + }) + + it("Should fail to revoke the multiple manual approvals -- because of input length mismatch", async() => { + await catchRevert( + I_ManualApprovalTransferManager.revokeManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3], + { + from: token_owner + } + ) + ); + }) + + it("Revoke multiple manual approvals", async () => { + await I_ManualApprovalTransferManager.revokeManualApprovalMulti( + [account_investor2,account_investor3], + [account_investor3,account_investor4], + { + from: token_owner + } + ); + assert.equal(await I_ManualApprovalTransferManager.getTotalApprovalsLength.call(), 0); + }); - await catchRevert(I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 })); - await increaseTime(1 + 24 * 60 * 60); - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("1", "ether"), { from: account_investor1 }); + it("Add a manual approval for a 5th investor from issuance", async () => { + await I_ManualApprovalTransferManager.addManualApproval( + "", + account_investor5, + web3.utils.toWei("2", "ether"), + latestTime() + duration.days(1), + "DESCRIPTION", + { + from: token_owner + } + ); }); it("Should successfully attach the CountTransferManager with the security token (count of 1)", async () => { @@ -532,16 +804,16 @@ contract("ManualApprovalTransferManager", accounts => { let name = web3.utils.toUtf8(await I_ManualApprovalTransferManagerFactory.getName.call()); assert.equal(name, "ManualApprovalTransferManager", "Wrong Module added"); let desc = await I_ManualApprovalTransferManagerFactory.description.call(); - assert.equal(desc, "Manage transfers using single approvals / blocking", "Wrong Module added"); + assert.equal(desc, "Manage transfers using single approvals", "Wrong Module added"); let title = await I_ManualApprovalTransferManagerFactory.title.call(); assert.equal(title, "Manual Approval Transfer Manager", "Wrong Module added"); let inst = await I_ManualApprovalTransferManagerFactory.getInstructions.call(); assert.equal( inst, - "Allows an issuer to set manual approvals or blocks for specific pairs of addresses and amounts. Init function takes no parameters.", + "Allows an issuer to set manual approvals for specific pairs of addresses and amounts. Init function takes no parameters.", "Wrong Module added" ); - assert.equal(await I_ManualApprovalTransferManagerFactory.version.call(), "2.0.1"); + assert.equal(await I_ManualApprovalTransferManagerFactory.version.call(), "2.1.0"); }); it("Should get the tags of the factory", async () => { diff --git a/test/k_module_registry.js b/test/k_module_registry.js index 3ec2d0a0d..ba3d55155 100644 --- a/test/k_module_registry.js +++ b/test/k_module_registry.js @@ -199,33 +199,33 @@ contract("ModuleRegistry", accounts => { it("Should successfully update the registry contract addresses", async () => { await I_MRProxied.updateFromRegistry({ from: account_polymath }); assert.equal( - await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("securityTokenRegistry")), + await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("securityTokenRegistry")), I_SecurityTokenRegistryProxy.address ); assert.equal( - await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("featureRegistry")), + await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("featureRegistry")), I_FeatureRegistry.address ); - assert.equal(await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("polyToken")), I_PolyToken.address); + assert.equal(await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("polyToken")), I_PolyToken.address); }); }); describe("Test the state variables", async () => { it("Should be the right owner", async () => { - let _owner = await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("owner")); + let _owner = await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("owner")); assert.equal(_owner, account_polymath, "Owner should be the correct"); }); it("Should be the expected value of the paused and intialised variable", async () => { - let _paused = await I_MRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")); + let _paused = await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")); assert.isFalse(_paused, "Should be the false"); - let _intialised = await I_MRProxied.getBoolValues.call(web3.utils.soliditySha3("initialised")); + let _intialised = await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("initialised")); assert.isTrue(_intialised, "Values should be the true"); }); it("Should be the expected value of the polymath registry", async () => { - let _polymathRegistry = await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("polymathRegistry")); + let _polymathRegistry = await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("polymathRegistry")); assert.equal( _polymathRegistry, I_PolymathRegistry.address, @@ -496,10 +496,10 @@ contract("ModuleRegistry", accounts => { // re-ordering assert.equal(sto3_end, sto3); // delete related data - assert.equal(await I_MRProxied.getUintValues.call(web3.utils.soliditySha3("registry", sto4)), 0); + assert.equal(await I_MRProxied.getUintValue.call(web3.utils.soliditySha3("registry", sto4)), 0); assert.equal(await I_MRProxied.getReputationByFactory.call(sto4), 0); assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 3); - assert.equal(await I_MRProxied.getBoolValues.call(web3.utils.soliditySha3("verified", sto4)), false); + assert.equal(await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("verified", sto4)), false); await revertToSnapshot(snap); }); @@ -522,10 +522,10 @@ contract("ModuleRegistry", accounts => { // re-ordering assert.equal(sto1_end, sto1); // delete related data - assert.equal(await I_MRProxied.getUintValues.call(web3.utils.soliditySha3("registry", sto2)), 0); + assert.equal(await I_MRProxied.getUintValue.call(web3.utils.soliditySha3("registry", sto2)), 0); assert.equal(await I_MRProxied.getReputationByFactory.call(sto2), 0); assert.equal((await I_MRProxied.getModulesByType.call(3)).length, 3); - assert.equal(await I_MRProxied.getBoolValues.call(web3.utils.soliditySha3("verified", sto2)), false); + assert.equal(await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("verified", sto2)), false); }); it("Should fail if module already removed", async () => { @@ -568,7 +568,7 @@ contract("ModuleRegistry", accounts => { it("Should successfully pause the contract", async () => { await I_MRProxied.pause({ from: account_polymath }); - let status = await I_MRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")); + let status = await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")); assert.isOk(status); }); @@ -578,7 +578,7 @@ contract("ModuleRegistry", accounts => { it("Should successfully unpause the contract", async () => { await I_MRProxied.unpause({ from: account_polymath }); - let status = await I_MRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")); + let status = await I_MRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")); assert.isNotOk(status); }); }); diff --git a/test/l_percentage_transfer_manager.js b/test/l_percentage_transfer_manager.js index b15ee41a0..e122bdea2 100644 --- a/test/l_percentage_transfer_manager.js +++ b/test/l_percentage_transfer_manager.js @@ -120,7 +120,7 @@ contract("PercentageTransferManager", accounts => { // STEP 4(b): Deploy the PercentageTransferManager [P_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); - + // Printing all the contract addresses console.log(` --------------------- Polymath Network Smart Contracts: --------------------- diff --git a/test/n_security_token_registry.js b/test/n_security_token_registry.js index de8b7d733..4bea414db 100644 --- a/test/n_security_token_registry.js +++ b/test/n_security_token_registry.js @@ -291,25 +291,25 @@ contract("SecurityTokenRegistry", accounts => { describe(" Test cases of the registerTicker", async () => { it("verify the intial parameters", async () => { - let intialised = await I_STRProxied.getBoolValues.call(web3.utils.soliditySha3("initialised")); + let intialised = await I_STRProxied.getBoolValue.call(web3.utils.soliditySha3("initialised")); assert.isTrue(intialised, "Should be true"); - let expiry = await I_STRProxied.getUintValues.call(web3.utils.soliditySha3("expiryLimit")); + let expiry = await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("expiryLimit")); assert.equal(expiry.toNumber(), 5184000, "Expiry limit should be equal to 60 days"); - let polytoken = await I_STRProxied.getAddressValues.call(web3.utils.soliditySha3("polyToken")); + let polytoken = await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("polyToken")); assert.equal(polytoken, I_PolyToken.address, "Should be the polytoken address"); - let stlaunchFee = await I_STRProxied.getUintValues.call(web3.utils.soliditySha3("stLaunchFee")); + let stlaunchFee = await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("stLaunchFee")); assert.equal(stlaunchFee.toNumber(), initRegFee, "Should be provided reg fee"); - let tickerRegFee = await I_STRProxied.getUintValues.call(web3.utils.soliditySha3("tickerRegFee")); + let tickerRegFee = await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("tickerRegFee")); assert.equal(tickerRegFee.toNumber(), tickerRegFee, "Should be provided reg fee"); - let polymathRegistry = await I_STRProxied.getAddressValues.call(web3.utils.soliditySha3("polymathRegistry")); + let polymathRegistry = await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("polymathRegistry")); assert.equal(polymathRegistry, I_PolymathRegistry.address, "Should be the address of the polymath registry"); - let owner = await I_STRProxied.getAddressValues.call(web3.utils.soliditySha3("owner")); + let owner = await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("owner")); assert.equal(owner, account_polymath, "Should be the address of the registry owner"); }); @@ -424,7 +424,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should successfully set the expiry limit", async () => { await I_STRProxied.changeExpiryLimit(duration.days(10), { from: account_polymath }); assert.equal( - (await I_STRProxied.getUintValues.call(web3.utils.soliditySha3("expiryLimit"))).toNumber(), + (await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("expiryLimit"))).toNumber(), duration.days(10), "Failed to change the expiry limit" ); @@ -619,7 +619,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should upgrade the logic contract into the STRProxy", async () => { await I_SecurityTokenRegistryProxy.upgradeTo("1.1.0", I_SecurityTokenRegistryV2.address, { from: account_polymath }); I_STRProxied = await SecurityTokenRegistry.at(I_SecurityTokenRegistryProxy.address); - assert.isTrue(await I_STRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")), "Paused value should be false"); + assert.isTrue(await I_STRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")), "Paused value should be false"); }); it("Should check the old data persist or not", async () => { @@ -631,7 +631,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should unpause the logic contract", async () => { await I_STRProxied.unpause({ from: account_polymath }); - assert.isFalse(await I_STRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")), "Paused value should be false"); + assert.isFalse(await I_STRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")), "Paused value should be false"); }); }); @@ -873,7 +873,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should able to change the STLaunchFee", async () => { let tx = await I_STRProxied.changeSecurityLaunchFee(web3.utils.toWei("500"), { from: account_polymath }); assert.equal(tx.logs[0].args._newFee, web3.utils.toWei("500")); - let stLaunchFee = await I_STRProxied.getUintValues(web3.utils.soliditySha3("stLaunchFee")); + let stLaunchFee = await I_STRProxied.getUintValue(web3.utils.soliditySha3("stLaunchFee")); assert.equal(stLaunchFee, web3.utils.toWei("500")); }); }); @@ -896,7 +896,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should able to change the ExpiryLimit", async () => { let tx = await I_STRProxied.changeExpiryLimit(duration.days(20), { from: account_polymath }); assert.equal(tx.logs[0].args._newExpiry, duration.days(20)); - let expiry = await I_STRProxied.getUintValues(web3.utils.soliditySha3("expiryLimit")); + let expiry = await I_STRProxied.getUintValue(web3.utils.soliditySha3("expiryLimit")); assert.equal(expiry, duration.days(20)); }); }); @@ -919,7 +919,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should able to change the TickerRegFee", async () => { let tx = await I_STRProxied.changeTickerRegistrationFee(web3.utils.toWei("400"), { from: account_polymath }); assert.equal(tx.logs[0].args._newFee, web3.utils.toWei("400")); - let tickerRegFee = await I_STRProxied.getUintValues(web3.utils.soliditySha3("tickerRegFee")); + let tickerRegFee = await I_STRProxied.getUintValue(web3.utils.soliditySha3("tickerRegFee")); assert.equal(tickerRegFee, web3.utils.toWei("400")); }); @@ -974,7 +974,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should successfully change the polytoken address", async () => { let _id = await takeSnapshot(); await I_STRProxied.updatePolyTokenAddress(dummy_token, { from: account_polymath }); - assert.equal(await I_STRProxied.getAddressValues.call(web3.utils.soliditySha3("polyToken")), dummy_token); + assert.equal(await I_STRProxied.getAddressValue.call(web3.utils.soliditySha3("polyToken")), dummy_token); await revertToSnapshot(_id); }); }); @@ -1122,7 +1122,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should successfully pause the contract", async () => { await I_STRProxied.pause({ from: account_polymath }); - let status = await I_STRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")); + let status = await I_STRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")); assert.isOk(status); }); @@ -1132,7 +1132,7 @@ contract("SecurityTokenRegistry", accounts => { it("Should successfully unpause the contract", async () => { await I_STRProxied.unpause({ from: account_polymath }); - let status = await I_STRProxied.getBoolValues.call(web3.utils.soliditySha3("paused")); + let status = await I_STRProxied.getBoolValue.call(web3.utils.soliditySha3("paused")); assert.isNotOk(status); }); }); diff --git a/test/o_security_token.js b/test/o_security_token.js index b43db486d..84aed05f3 100644 --- a/test/o_security_token.js +++ b/test/o_security_token.js @@ -93,7 +93,7 @@ contract("SecurityToken", accounts => { let startTime; let endTime; const cap = web3.utils.toWei("10000"); - const rate = 1000; + const rate = web3.utils.toWei("1000"); const fundRaiseType = [0]; const cappedSTOSetupCost = web3.utils.toWei("20000", "ether"); const maxCost = cappedSTOSetupCost; diff --git a/test/p_usd_tiered_sto.js b/test/p_usd_tiered_sto.js index b9c825b2c..ee5838d0a 100644 --- a/test/p_usd_tiered_sto.js +++ b/test/p_usd_tiered_sto.js @@ -157,16 +157,16 @@ contract("USDTieredSTO", accounts => { name: "_reserveWallet" }, { - type: "address", - name: "_usdToken" + type: "address[]", + name: "_usdTokens" } ] }; async function convert(_stoID, _tier, _discount, _currencyFrom, _currencyTo, _amount) { let USDTOKEN; - if (_discount) USDTOKEN = await I_USDTieredSTO_Array[_stoID].ratePerTierDiscountPoly.call(_tier); - else USDTOKEN = await I_USDTieredSTO_Array[_stoID].ratePerTier.call(_tier); + if (_discount) USDTOKEN = ((await I_USDTieredSTO_Array[_stoID].tiers.call(_tier))[1]); + else USDTOKEN = ((await I_USDTieredSTO_Array[_stoID].tiers.call(_tier))[0]); if (_currencyFrom == "TOKEN") { let tokenToUSD = _amount .div(10 ** 18) @@ -222,7 +222,7 @@ contract("USDTieredSTO", accounts => { I_SecurityTokenRegistryProxy, I_STRProxied ] = instances; - + I_DaiToken = await PolyTokenFaucet.new({from: POLYMATH}); // STEP 4: Deploy the GeneralDelegateManagerFactory [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(POLYMATH, I_MRProxied, I_PolyToken.address, 0); @@ -301,7 +301,7 @@ contract("USDTieredSTO", accounts => { _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); _reserveWallet.push(RESERVEWALLET); - _usdToken.push(I_DaiToken.address); + _usdToken.push([I_DaiToken.address]); let config = [ _startTime[stoId], @@ -325,26 +325,26 @@ contract("USDTieredSTO", accounts => { assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); - assert.equal(await I_USDTieredSTO_Array[stoId].startTime.call(), _startTime[stoId], "Incorrect _startTime in config"); + assert.equal((await I_USDTieredSTO_Array[stoId].startTime.call()).toNumber(), _startTime[stoId], "Incorrect _startTime in config"); assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), _endTime[stoId], "Incorrect _endTime in config"); for (var i = 0; i < _ratePerTier[stoId].length; i++) { assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTier.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toNumber(), _ratePerTier[stoId][i].toNumber(), "Incorrect _ratePerTier in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTierDiscountPoly.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toNumber(), _ratePerTierDiscountPoly[stoId][i].toNumber(), "Incorrect _ratePerTierDiscountPoly in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toNumber(), _tokensPerTierTotal[stoId][i].toNumber(), "Incorrect _tokensPerTierTotal in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tokensPerTierDiscountPoly.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toNumber(), _tokensPerTierDiscountPoly[stoId][i].toNumber(), "Incorrect _tokensPerTierDiscountPoly in config" ); @@ -365,7 +365,7 @@ contract("USDTieredSTO", accounts => { _reserveWallet[stoId], "Incorrect _reserveWallet in config" ); - assert.equal(await I_USDTieredSTO_Array[stoId].usdToken.call(), _usdToken[stoId], "Incorrect _usdToken in config"); + assert.equal(await I_USDTieredSTO_Array[stoId].usdTokens.call(0), _usdToken[stoId][0], "Incorrect _usdToken in config"); assert.equal( await I_USDTieredSTO_Array[stoId].getNumberOfTiers(), _tokensPerTierTotal[stoId].length, @@ -486,7 +486,7 @@ contract("USDTieredSTO", accounts => { _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); _reserveWallet.push(RESERVEWALLET); - _usdToken.push(I_DaiToken.address); + _usdToken.push([I_DaiToken.address]); let config = [ _startTime[stoId], @@ -514,22 +514,22 @@ contract("USDTieredSTO", accounts => { assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), _endTime[stoId], "Incorrect _endTime in config"); for (var i = 0; i < _ratePerTier[stoId].length; i++) { assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTier.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toNumber(), _ratePerTier[stoId][i].toNumber(), "Incorrect _ratePerTier in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTierDiscountPoly.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toNumber(), _ratePerTierDiscountPoly[stoId][i].toNumber(), "Incorrect _ratePerTierDiscountPoly in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toNumber(), _tokensPerTierTotal[stoId][i].toNumber(), "Incorrect _tokensPerTierTotal in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tokensPerTierDiscountPoly.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toNumber(), _tokensPerTierDiscountPoly[stoId][i].toNumber(), "Incorrect _tokensPerTierDiscountPoly in config" ); @@ -550,7 +550,7 @@ contract("USDTieredSTO", accounts => { _reserveWallet[stoId], "Incorrect _reserveWallet in config" ); - assert.equal(await I_USDTieredSTO_Array[stoId].usdToken.call(), _usdToken[stoId], "Incorrect _usdToken in config"); + assert.equal(await I_USDTieredSTO_Array[stoId].usdTokens.call(0), _usdToken[stoId][0], "Incorrect _usdToken in config"); assert.equal( await I_USDTieredSTO_Array[stoId].getNumberOfTiers(), _tokensPerTierTotal[stoId].length, @@ -573,7 +573,7 @@ contract("USDTieredSTO", accounts => { _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); _reserveWallet.push(RESERVEWALLET); - _usdToken.push(I_DaiToken.address); + _usdToken.push([I_DaiToken.address]); let config = [ _startTime[stoId], @@ -612,7 +612,87 @@ contract("USDTieredSTO", accounts => { _fundRaiseTypes.push([0, 1, 2]); _wallet.push(WALLET); _reserveWallet.push(RESERVEWALLET); - _usdToken.push(I_DaiToken.address); + _usdToken.push([I_DaiToken.address]); + + let config = [ + _startTime[stoId], + _endTime[stoId], + _ratePerTier[stoId], + _ratePerTierDiscountPoly[stoId], + _tokensPerTierTotal[stoId], + _tokensPerTierDiscountPoly[stoId], + _nonAccreditedLimitUSD[stoId], + _minimumInvestmentUSD[stoId], + _fundRaiseTypes[stoId], + _wallet[stoId], + _reserveWallet[stoId], + _usdToken[stoId] + ]; + + let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); + assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); + I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + let tokens = await I_USDTieredSTO_Array[I_USDTieredSTO_Array.length - 1].getUsdTokens.call(); + assert.equal(tokens[0], I_DaiToken.address, "USD Tokens should match"); + }); + + it("Should successfully attach the fifth STO module to the security token", async () => { + let stoId = 4; // Non-divisible tokens + + _startTime.push(latestTime() + duration.days(2)); + _endTime.push(_startTime[stoId] + duration.days(100)); + _ratePerTier.push([BigNumber(1 * 10 ** 18), BigNumber(1.5 * 10 ** 18)]); // [ 1 USD/Token, 1.5 USD/Token ] + _ratePerTierDiscountPoly.push([BigNumber(0.5 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 0.5 USD/Token, 1.5 USD/Token ] + _tokensPerTierTotal.push([BigNumber(100 * 10 ** 18), BigNumber(50 * 10 ** 18)]); // [ 100 Token, 50 Token ] + _tokensPerTierDiscountPoly.push([BigNumber(100 * 10 ** 18), BigNumber(25 * 10 ** 18)]); // [ 100 Token, 25 Token ] + _nonAccreditedLimitUSD.push(BigNumber(25 * 10 ** 18)); // [ 25 USD ] + _minimumInvestmentUSD.push(BigNumber(5)); + _fundRaiseTypes.push([0, 1, 2]); + _wallet.push(WALLET); + _reserveWallet.push(RESERVEWALLET); + _usdToken.push([I_DaiToken.address]); + + let config = [ + _startTime[stoId], + _endTime[stoId], + _ratePerTier[stoId], + _ratePerTierDiscountPoly[stoId], + _tokensPerTierTotal[stoId], + _tokensPerTierDiscountPoly[stoId], + _nonAccreditedLimitUSD[stoId], + _minimumInvestmentUSD[stoId], + _fundRaiseTypes[stoId], + _wallet[stoId], + _reserveWallet[stoId], + _usdToken[stoId] + ]; + + let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); + let tx = await I_SecurityToken.addModule(I_USDTieredSTOFactory.address, bytesSTO, 0, 0, { from: ISSUER, gasPrice: GAS_PRICE }); + console.log(" Gas addModule: ".grey + tx.receipt.gasUsed.toString().grey); + assert.equal(tx.logs[2].args._types[0], STOKEY, "USDTieredSTO doesn't get deployed"); + assert.equal(web3.utils.hexToString(tx.logs[2].args._name), "USDTieredSTO", "USDTieredSTOFactory module was not added"); + I_USDTieredSTO_Array.push(USDTieredSTO.at(tx.logs[2].args._module)); + }); + + it("Should successfully attach the sixth STO module to the security token", async () => { + let stoId = 5; // Non-divisible tokens with bad granularity in tiers + + _startTime.push(latestTime() + duration.days(2)); + _endTime.push(_startTime[stoId] + duration.days(100)); + _ratePerTier.push([BigNumber(1 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 1 USD/Token, 1 USD/Token ] + _ratePerTierDiscountPoly.push([BigNumber(1 * 10 ** 18), BigNumber(1 * 10 ** 18)]); // [ 1 USD/Token, 1 USD/Token ] + _tokensPerTierTotal.push([BigNumber(1001 * 10 ** 17), BigNumber(50 * 10 ** 18)]); // [ 100.1 Token, 50 Token ] + _tokensPerTierDiscountPoly.push([BigNumber(0), BigNumber(0)]); // [ 0 Token, 0 Token ] + _nonAccreditedLimitUSD.push(BigNumber(25 * 10 ** 18)); // [ 25 USD ] + _minimumInvestmentUSD.push(BigNumber(5)); + _fundRaiseTypes.push([0, 1, 2]); + _wallet.push(WALLET); + _reserveWallet.push(RESERVEWALLET); + _usdToken.push([I_DaiToken.address]); let config = [ _startTime[stoId], @@ -770,7 +850,8 @@ contract("USDTieredSTO", accounts => { _minimumInvestmentUSD[stoId], _fundRaiseTypes[stoId], _wallet[stoId], - reserveWallet + reserveWallet, + _usdToken[stoId] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); @@ -865,22 +946,22 @@ contract("USDTieredSTO", accounts => { { from: ISSUER } ); assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTier.call(0)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[0].toNumber(), BigNumber(15 * 10 ** 18).toNumber(), "STO Configuration doesn't set as expected" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTierDiscountPoly.call(0)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[1].toNumber(), BigNumber(13 * 10 ** 18).toNumber(), "STO Configuration doesn't set as expected" ); assert.equal( - await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(0), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[2], BigNumber(15 * 10 ** 20).toNumber(), "STO Configuration doesn't set as expected" ); assert.equal( - await I_USDTieredSTO_Array[stoId].tokensPerTierDiscountPoly.call(0), + (await I_USDTieredSTO_Array[stoId].tiers.call(0))[3], BigNumber(15 * 10 ** 20).toNumber(), "STO Configuration doesn't set as expected" ); @@ -895,7 +976,7 @@ contract("USDTieredSTO", accounts => { await I_USDTieredSTO_Array[stoId].modifyAddresses( "0x0000000000000000000000000400000000000000", "0x0000000000000000000003000000000000000000", - address_zero, + [0x0000000000000000000003000000000000057a00], { from: ISSUER } ); assert.equal( @@ -909,8 +990,8 @@ contract("USDTieredSTO", accounts => { "STO Configuration doesn't set as expected" ); assert.equal( - await I_USDTieredSTO_Array[stoId].usdToken.call(), - address_zero, + await I_USDTieredSTO_Array[stoId].usdTokens.call(0), + "0x0000000000000000000003000000000000057a00", "STO Configuration doesn't set as expected" ); }); @@ -937,20 +1018,11 @@ contract("USDTieredSTO", accounts => { ) ); - let tempTime1 = latestTime(); + let tempTime1 = latestTime() + duration.days(1); let tempTime2 = latestTime() + duration.days(3); await catchRevert(I_USDTieredSTO_Array[stoId].modifyTimes(tempTime1, tempTime2, { from: ISSUER })); - await catchRevert( - I_USDTieredSTO_Array[stoId].modifyAddresses( - "0x0000000000000000000000000400000000000000", - "0x0000000000000000000003000000000000000000", - I_DaiToken.address, - { from: ISSUER } - ) - ); - await revertToSnapshot(snapId); }); }); @@ -989,25 +1061,18 @@ contract("USDTieredSTO", accounts => { await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: ACCREDITED1 }); - // NONACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(NONACCREDITED1, { from: NONACCREDITED1, value: investment_ETH })); - // NONACCREDITED POLY await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 })); - // NONACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 })); - + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); // ACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH })); - // ACCREDITED POLY await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 })); - // ACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 })); - + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); await revertToSnapshot(snapId); }); @@ -1050,7 +1115,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 })); // NONACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); // ACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH })); @@ -1059,7 +1124,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 })); // ACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); await revertToSnapshot(snapId); }); @@ -1106,7 +1171,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 })); // NONACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); // ACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH })); @@ -1115,7 +1180,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 })); // ACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); await revertToSnapshot(snapId); }); @@ -1164,7 +1229,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 })); // NONACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); // ACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH })); @@ -1173,7 +1238,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 })); // ACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); // Unpause the STO await I_USDTieredSTO_Array[stoId].unpause({ from: ISSUER }); @@ -1181,11 +1246,59 @@ contract("USDTieredSTO", accounts => { await I_USDTieredSTO_Array[stoId].buyWithETH(NONACCREDITED1, { from: NONACCREDITED1, value: investment_ETH }); await I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 }); - await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 }); + await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 }); await I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH }); await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 }); - await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 }); + await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 }); + + await revertToSnapshot(snapId); + }); + + it("should allow changing stable coin address in middle of STO", async () => { + let stoId = 0; + let snapId = await takeSnapshot(); + + // Whitelist + let fromTime = latestTime(); + let toTime = latestTime() + duration.days(15); + let expiryTime = toTime + duration.days(100); + let whitelisted = true; + + await I_GeneralTransferManager.modifyWhitelist(ACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + await I_GeneralTransferManager.modifyWhitelist(NONACCREDITED1, fromTime, toTime, expiryTime, whitelisted, { from: ISSUER }); + + // Advance time to after STO start + await increaseTime(duration.days(3)); + + // Set as accredited + await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); + + // Prep for investments + let investment_DAI = web3.utils.toWei("500", "ether"); // Invest 10000 POLY + await I_DaiToken.getTokens(investment_DAI, NONACCREDITED1); + await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: NONACCREDITED1 }); + await I_DaiToken.getTokens(investment_DAI, ACCREDITED1); + await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: ACCREDITED1 }); + + // Make sure buying works before changing + await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 }); + + // Change Stable coin address + let I_DaiToken2 = await PolyTokenFaucet.new({from: POLYMATH}); + await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, RESERVEWALLET, [I_DaiToken2.address], { from: ISSUER }); + + // NONACCREDITED DAI + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); + + // ACCREDITED DAI + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); + + // Revert stable coin address + await I_USDTieredSTO_Array[stoId].modifyAddresses(WALLET, RESERVEWALLET, [I_DaiToken.address], { from: ISSUER }); + + // Make sure buying works again + await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 }); await revertToSnapshot(snapId); }); @@ -1231,7 +1344,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 })); // NONACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); // ACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH })); @@ -1240,7 +1353,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 })); // ACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); await revertToSnapshot(snapId); }); @@ -1293,7 +1406,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(NONACCREDITED1, investment_POLY, { from: NONACCREDITED1 })); // NONACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1 })); // ACCREDITED ETH await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { from: ACCREDITED1, value: investment_ETH })); @@ -1302,7 +1415,7 @@ contract("USDTieredSTO", accounts => { await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { from: ACCREDITED1 })); // ACCREDITED DAI - await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1 })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1 })); await revertToSnapshot(snapId); }); @@ -1335,20 +1448,31 @@ contract("USDTieredSTO", accounts => { it("should successfully modify accredited addresses for first STO", async () => { let stoId = 0; - - let status1 = await I_USDTieredSTO_Array[stoId].accredited.call(NONACCREDITED1); - assert.equal(status1, false, "Initial accreditation is set to true"); + let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); + let status1 = investorStatus[0].toNumber(); + assert.equal(status1, 0, "Initial accreditation is set to true"); await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1], [true], { from: ISSUER }); - let status2 = await I_USDTieredSTO_Array[stoId].accredited.call(NONACCREDITED1); - assert.equal(status2, true, "Failed to set single address"); + investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); + let status2 = investorStatus[0].toNumber(); + assert.equal(status2, 1, "Failed to set single address"); await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, ACCREDITED1], [false, true], { from: ISSUER }); - let status3 = await I_USDTieredSTO_Array[stoId].accredited.call(NONACCREDITED1); - assert.equal(status3, false, "Failed to set multiple addresses"); - let status4 = await I_USDTieredSTO_Array[stoId].accredited.call(ACCREDITED1); - assert.equal(status4, true, "Failed to set multiple addresses"); - + investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); + let status3 = investorStatus[0].toNumber(); + assert.equal(status3, 0, "Failed to set multiple addresses"); + investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(ACCREDITED1); + let status4 = investorStatus[0].toNumber(); + assert.equal(status4, 1, "Failed to set multiple addresses"); + + let totalStatus = await I_USDTieredSTO_Array[stoId].getAccreditedData.call(); + + assert.equal(totalStatus[0][0], NONACCREDITED1, "Account match"); + assert.equal(totalStatus[0][1], ACCREDITED1, "Account match"); + assert.equal(totalStatus[1][0], false, "Account match"); + assert.equal(totalStatus[1][1], true, "Account match"); + assert.equal(totalStatus[2][0].toNumber(), 0, "override match"); + assert.equal(totalStatus[2][1].toNumber(), 0, "override match"); await catchRevert(I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, ACCREDITED1], [true], { from: ISSUER })); }); @@ -1356,10 +1480,12 @@ contract("USDTieredSTO", accounts => { let stoId = 1; await I_USDTieredSTO_Array[stoId].changeAccredited([NONACCREDITED1, ACCREDITED1], [false, true], { from: ISSUER }); - let status1 = await I_USDTieredSTO_Array[stoId].accredited.call(NONACCREDITED1); - let status2 = await I_USDTieredSTO_Array[stoId].accredited.call(ACCREDITED1); - assert.equal(status1, false, "Failed to set multiple address"); - assert.equal(status2, true, "Failed to set multiple address"); + let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); + let status1 = investorStatus[0].toNumber(); + investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(ACCREDITED1); + let status2 = investorStatus[0].toNumber(); + assert.equal(status1, 0, "Failed to set multiple address"); + assert.equal(status2, 1, "Failed to set multiple address"); }); }); @@ -1692,7 +1818,7 @@ contract("USDTieredSTO", accounts => { let init_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); // Buy With DAI - let tx2 = await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { + let tx2 = await I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1, gasPrice: GAS_PRICE }); @@ -1756,6 +1882,11 @@ contract("USDTieredSTO", accounts => { init_WalletDAIBal.add(investment_DAI).toNumber(), "Wallet DAI Balance not changed as expected" ); + assert.equal( + (await I_USDTieredSTO_Array[stoId].stableCoinsRaised.call(I_DaiToken.address)).toNumber(), + investment_DAI.toNumber(), + "DAI Raised not changed as expected" + ); }); it("should successfully buy using fallback at tier 0 for ACCREDITED1", async () => { @@ -2063,14 +2194,25 @@ contract("USDTieredSTO", accounts => { await I_USDTieredSTO_Array[stoId].changeNonAccreditedLimit([NONACCREDITED1], [_nonAccreditedLimitUSD[stoId].div(2)], { from: ISSUER }); - console.log("Current limit: " + (await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride(NONACCREDITED1)).toNumber()); + let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); + console.log("Current limit: " + investorStatus[2].toNumber()); + let totalStatus = await I_USDTieredSTO_Array[stoId].getAccreditedData.call(); + + assert.equal(totalStatus[0][0], NONACCREDITED1, "Account match"); + assert.equal(totalStatus[0][1], ACCREDITED1, "Account match"); + assert.equal(totalStatus[1][0], false, "Account match"); + assert.equal(totalStatus[1][1], true, "Account match"); + assert.equal(totalStatus[2][0].toNumber(), _nonAccreditedLimitUSD[stoId].div(2), "override match"); + assert.equal(totalStatus[2][1].toNumber(), 0, "override match"); + }); it("should successfully buy a partial amount and refund balance when reaching NONACCREDITED cap", async () => { let stoId = 0; let tierId = 0; - let investment_USD = await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride(NONACCREDITED1); //_nonAccreditedLimitUSD[stoId]; + let investorStatus = await I_USDTieredSTO_Array[stoId].investors.call(NONACCREDITED1); + let investment_USD = investorStatus[2];//await I_USDTieredSTO_Array[stoId].nonAccreditedLimitUSDOverride(NONACCREDITED1); //_nonAccreditedLimitUSD[stoId]; let investment_Token = await convert(stoId, tierId, false, "USD", "TOKEN", investment_USD); let investment_ETH = await convert(stoId, tierId, false, "USD", "ETH", investment_USD); let investment_POLY = await convert(stoId, tierId, false, "USD", "POLY", investment_USD); @@ -2683,7 +2825,7 @@ contract("USDTieredSTO", accounts => { let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); let init_WalletDAIBal = await I_DaiToken.balanceOf(WALLET); - let tx2 = await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1, gasPrice: GAS_PRICE }); + let tx2 = await I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1, gasPrice: GAS_PRICE }); let gasCost2 = new BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); console.log(" Gas buyWithUSD: ".grey + tx2.receipt.gasUsed.toString().grey); @@ -2850,7 +2992,7 @@ contract("USDTieredSTO", accounts => { let stoId = 1; let tierId = 5; - let minted = await I_USDTieredSTO_Array[stoId].mintedPerTierTotal.call(tierId); + let minted = (await I_USDTieredSTO_Array[stoId].tiers.call(tierId))[4]; console.log(minted.toNumber() + ":" + _tokensPerTierTotal[stoId][tierId]); let investment_Token = _tokensPerTierTotal[stoId][tierId].sub(minted); console.log(investment_Token.toNumber()); @@ -2922,7 +3064,7 @@ contract("USDTieredSTO", accounts => { // Buy with DAI NONACCREDITED await catchRevert( - I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, { from: NONACCREDITED1, gasPrice: GAS_PRICE }) + I_USDTieredSTO_Array[stoId].buyWithUSD(NONACCREDITED1, investment_DAI, I_DaiToken.address, { from: NONACCREDITED1, gasPrice: GAS_PRICE }) ); // Buy with ETH ACCREDITED @@ -2940,7 +3082,7 @@ contract("USDTieredSTO", accounts => { // Buy with DAI ACCREDITED await catchRevert( - I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, { from: ACCREDITED1, gasPrice: GAS_PRICE }) + I_USDTieredSTO_Array[stoId].buyWithUSD(ACCREDITED1, investment_DAI, I_DaiToken.address, { from: ACCREDITED1, gasPrice: GAS_PRICE }) ); }); @@ -3732,6 +3874,308 @@ contract("USDTieredSTO", accounts => { ); }); + it("should successfully buy a granular amount and refund balance when buying indivisible token with POLY", async () => { + await I_SecurityToken.changeGranularity(10 ** 18, {from: ISSUER}); + let stoId = 4; + let tierId = 0; + await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); + let investment_Tokens = (new BigNumber(10.5)).mul(10 ** 18); + let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Tokens); + + let refund_Tokens = (new BigNumber(0.5)).mul(10 ** 18); + let refund_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", refund_Tokens); + + await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); + await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); + + let init_TokenSupply = await I_SecurityToken.totalSupply(); + let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); + let init_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); + let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); + let init_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); + let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); + let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); + let init_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); + + let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyTokensView(ACCREDITED1, investment_POLY,POLY))[2]; + + // Buy With POLY + let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { + from: ACCREDITED1, + gasPrice: GAS_PRICE + }); + let gasCost2 = BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); + console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + + let final_TokenSupply = await I_SecurityToken.totalSupply(); + let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); + let final_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); + let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); + let final_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); + let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); + let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); + let final_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); + + assert.equal( + final_TokenSupply.toNumber(), + init_TokenSupply + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "Token Supply not changed as expected" + ); + assert.equal( + tokensToMint.toNumber(), + investment_Tokens.sub(refund_Tokens).toNumber(), + "View function returned incorrect data" + ); + assert.equal( + final_InvestorTokenBal.toNumber(), + init_InvestorTokenBal + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "Investor Token Balance not changed as expected" + ); + assert.equal( + final_InvestorETHBal.toNumber(), + init_InvestorETHBal.sub(gasCost2).toNumber(), + "Investor ETH Balance not changed as expected" + ); + assert.equal( + final_InvestorPOLYBal.toNumber(), + init_InvestorPOLYBal + .sub(investment_POLY) + .add(refund_POLY) + .toNumber(), + "Investor POLY Balance not changed as expected" + ); + assert.equal( + final_STOTokenSold.toNumber(), + init_STOTokenSold + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "STO Token Sold not changed as expected" + ); + assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal( + final_RaisedPOLY.toNumber(), + init_RaisedPOLY + .add(investment_POLY) + .sub(refund_POLY) + .toNumber(), + "Raised POLY not changed as expected" + ); + assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal( + final_WalletPOLYBal.toNumber(), + init_WalletPOLYBal + .add(investment_POLY) + .sub(refund_POLY) + .toNumber(), + "Wallet POLY Balance not changed as expected" + ); + await I_SecurityToken.changeGranularity(1, {from: ISSUER}); + }); + + it("should successfully buy a granular amount when buying indivisible token accross tiers with invalid tier limit", async () => { + let stoId = 5; + let tierId = 0; + await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); + let investment_Tokens = (new BigNumber(110)).mul(10 ** 18); + let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Tokens); + + let refund_Tokens = new BigNumber(0); + let refund_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", refund_Tokens); + + await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); + await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); + + let init_TokenSupply = await I_SecurityToken.totalSupply(); + let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); + let init_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); + let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); + let init_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); + let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); + let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); + let init_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let init_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); + + let tokensToMint = (await I_USDTieredSTO_Array[stoId].buyTokensView(ACCREDITED1, investment_POLY,POLY))[2]; + + // Buy With POLY + let tx2 = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, investment_POLY, { + from: ACCREDITED1, + gasPrice: GAS_PRICE + }); + let gasCost2 = BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); + console.log(" Gas buyWithPOLY: ".grey + tx2.receipt.gasUsed.toString().grey); + + let final_TokenSupply = await I_SecurityToken.totalSupply(); + let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); + let final_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_InvestorPOLYBal = await I_PolyToken.balanceOf(ACCREDITED1); + let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); + let final_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); + let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); + let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); + let final_WalletETHBal = BigNumber(await web3.eth.getBalance(WALLET)); + let final_WalletPOLYBal = await I_PolyToken.balanceOf(WALLET); + + assert.equal( + final_TokenSupply.toNumber(), + init_TokenSupply + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "Token Supply not changed as expected" + ); + assert.equal( + tokensToMint.toNumber(), + investment_Tokens.sub(refund_Tokens).toNumber(), + "View function returned incorrect data" + ); + assert.equal( + final_InvestorTokenBal.toNumber(), + init_InvestorTokenBal + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "Investor Token Balance not changed as expected" + ); + assert.equal( + final_InvestorETHBal.toNumber(), + init_InvestorETHBal.sub(gasCost2).toNumber(), + "Investor ETH Balance not changed as expected" + ); + assert.equal( + final_InvestorPOLYBal.toNumber(), + init_InvestorPOLYBal + .sub(investment_POLY) + .add(refund_POLY) + .toNumber(), + "Investor POLY Balance not changed as expected" + ); + assert.equal( + final_STOTokenSold.toNumber(), + init_STOTokenSold + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "STO Token Sold not changed as expected" + ); + assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.toNumber(), "Raised ETH not changed as expected"); + assert.equal( + final_RaisedPOLY.toNumber(), + init_RaisedPOLY + .add(investment_POLY) + .sub(refund_POLY) + .toNumber(), + "Raised POLY not changed as expected" + ); + assert.equal(final_WalletETHBal.toNumber(), init_WalletETHBal.toNumber(), "Wallet ETH Balance not changed as expected"); + assert.equal( + final_WalletPOLYBal.toNumber(), + init_WalletPOLYBal + .add(investment_POLY) + .sub(refund_POLY) + .toNumber(), + "Wallet POLY Balance not changed as expected" + ); + await I_SecurityToken.changeGranularity(1, {from: ISSUER}); + }); + + it("should successfully buy a granular amount and refund balance when buying indivisible token with ETH", async () => { + await I_SecurityToken.changeGranularity(10**18, {from: ISSUER}); + let stoId = 4; + let tierId = 0; + let investment_Tokens = BigNumber(10.5).mul(10**18); + let investment_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", investment_Tokens); + let refund_Tokens = BigNumber(0.5).mul(10**18); + let refund_ETH = await convert(stoId, tierId, false, "TOKEN", "ETH", refund_Tokens); + + let init_TokenSupply = await I_SecurityToken.totalSupply(); + let init_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); + let init_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let init_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); + let init_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let init_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); + let init_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); + let init_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); + + + // Buy With ETH + let tx2 = await I_USDTieredSTO_Array[stoId].buyWithETH(ACCREDITED1, { + from: ACCREDITED1, + gasPrice: GAS_PRICE, + value: investment_ETH + }); + let gasCost2 = BigNumber(GAS_PRICE).mul(tx2.receipt.gasUsed); + console.log(" Gas buyWithETH: ".grey + tx2.receipt.gasUsed.toString().grey); + + let final_TokenSupply = await I_SecurityToken.totalSupply(); + let final_InvestorTokenBal = await I_SecurityToken.balanceOf(ACCREDITED1); + let final_InvestorETHBal = BigNumber(await web3.eth.getBalance(ACCREDITED1)); + let final_STOTokenSold = await I_USDTieredSTO_Array[stoId].getTokensSold(); + let final_STOETHBal = BigNumber(await web3.eth.getBalance(I_USDTieredSTO_Array[stoId].address)); + let final_STOPOLYBal = await I_PolyToken.balanceOf(I_USDTieredSTO_Array[stoId].address); + let final_RaisedETH = await I_USDTieredSTO_Array[stoId].fundsRaised.call(ETH); + let final_RaisedPOLY = await I_USDTieredSTO_Array[stoId].fundsRaised.call(POLY); + + assert.equal( + final_TokenSupply.toNumber(), + init_TokenSupply + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "Token Supply not changed as expected" + ); + assert.equal( + final_InvestorTokenBal.toNumber(), + init_InvestorTokenBal + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "Investor Token Balance not changed as expected" + ); + assert.equal( + final_InvestorETHBal.toNumber(), + init_InvestorETHBal.sub(investment_ETH).sub(gasCost2).add(refund_ETH).toNumber(), + "Investor ETH Balance not changed as expected" + ); + assert.equal( + final_STOTokenSold.toNumber(), + init_STOTokenSold + .add(investment_Tokens) + .sub(refund_Tokens) + .toNumber(), + "STO Token Sold not changed as expected" + ); + assert.equal(final_STOETHBal.toNumber(), init_STOETHBal.toNumber(), "STO ETH Balance not changed as expected"); + assert.equal(final_STOPOLYBal.toNumber(), init_STOPOLYBal.toNumber(), "STO POLY Balance not changed as expected"); + assert.equal(final_RaisedETH.toNumber(), init_RaisedETH.add(investment_ETH).sub(refund_ETH).toNumber(), "Raised ETH not changed as expected"); + assert.equal( + final_RaisedPOLY.toNumber(), + init_RaisedPOLY, + "Raised POLY not changed as expected" + ); + await I_SecurityToken.changeGranularity(1, {from: ISSUER}); + }); + it("should fail and revert when NONACCREDITED cap reached", async () => { let stoId = 2; let tierId = 0; @@ -3757,6 +4201,30 @@ contract("USDTieredSTO", accounts => { ); }); + it("should fail when rate set my contract is too low", async () => { + let stoId = 4; + let tierId = 0; + await I_USDTieredSTO_Array[stoId].changeAccredited([ACCREDITED1], [true], { from: ISSUER }); + let investment_Tokens = new BigNumber(10 ** 18); + let investment_POLY = await convert(stoId, tierId, true, "TOKEN", "POLY", investment_Tokens); + let investment_ETH = await convert(stoId, tierId, true, "TOKEN", "ETH", investment_Tokens); + const minTokens = new BigNumber(10 ** 20); + + await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); + await I_PolyToken.approve(I_USDTieredSTO_Array[stoId].address, investment_POLY, { from: ACCREDITED1 }); + + // Buy With POLY + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithPOLYRateLimited(ACCREDITED1, investment_POLY, minTokens, { + from: ACCREDITED1, + gasPrice: GAS_PRICE + })); + await catchRevert(I_USDTieredSTO_Array[stoId].buyWithETHRateLimited(ACCREDITED1, minTokens, { + from: ACCREDITED1, + gasPrice: GAS_PRICE, + value: investment_ETH + })); + }); + it("should fail and revert despite oracle price change when NONACCREDITED cap reached", async () => { let stoId = 2; let tierId = 0; @@ -3840,8 +4308,8 @@ contract("USDTieredSTO", accounts => { let investment_Token = delta_Token.add(delta_Token); // 10 Token let investment_POLY = polyTier0.add(polyTier1); // 0.0025 ETH - let tokensRemaining = (await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(startTier)).sub( - await I_USDTieredSTO_Array[stoId].mintedPerTierTotal.call(startTier) + let tokensRemaining = (await I_USDTieredSTO_Array[stoId].tiers.call(startTier))[2].sub( + (await I_USDTieredSTO_Array[stoId].tiers.call(startTier))[4] ); let prep_Token = tokensRemaining.sub(delta_Token); let prep_POLY = await convert(stoId, startTier, true, "TOKEN", "POLY", prep_Token); @@ -3851,8 +4319,8 @@ contract("USDTieredSTO", accounts => { let tx = await I_USDTieredSTO_Array[stoId].buyWithPOLY(ACCREDITED1, prep_POLY, { from: ACCREDITED1, gasPrice: GAS_PRICE }); console.log(" Gas buyWithPOLY: ".grey + tx.receipt.gasUsed.toString().grey); - let Tier0Token = await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(startTier); - let Tier0Minted = await I_USDTieredSTO_Array[stoId].mintedPerTierTotal.call(startTier); + let Tier0Token = (await I_USDTieredSTO_Array[stoId].tiers.call(startTier))[2]; + let Tier0Minted = (await I_USDTieredSTO_Array[stoId].tiers.call(startTier))[4]; assert.equal(Tier0Minted.toNumber(), Tier0Token.sub(delta_Token).toNumber()); await I_PolyToken.getTokens(investment_POLY, ACCREDITED1); @@ -4027,7 +4495,7 @@ contract("USDTieredSTO", accounts => { let stoId = 2; let tierId = 1; - let minted = await I_USDTieredSTO_Array[stoId].mintedPerTierTotal.call(tierId); + let minted = (await I_USDTieredSTO_Array[stoId].tiers.call(tierId))[4]; let investment_Token = _tokensPerTierTotal[stoId][tierId].sub(minted); let investment_POLY = await convert(stoId, tierId, false, "TOKEN", "POLY", investment_Token); @@ -4197,6 +4665,7 @@ contract("USDTieredSTO", accounts => { await I_USDOracle.changePrice(USDETH, { from: POLYMATH }); await I_POLYOracle.changePrice(USDPOLY, { from: POLYMATH }); }); + }); describe("Test getter functions", async () => { @@ -4236,6 +4705,18 @@ contract("USDTieredSTO", accounts => { "fundsRaisedUSD not changed as expected" ); }); + + it("should return minted tokens in a tier", async () => { + let totalMinted = (await I_USDTieredSTO_Array[0].getTokensSoldByTier.call(0)).toNumber(); + let individualMinted = await I_USDTieredSTO_Array[0].getTokensMintedByTier.call(0); + assert.equal(totalMinted, individualMinted[0].add(individualMinted[1]).add(individualMinted[2]).toNumber()); + }); + + it("should return correct tokens sold in token details", async () => { + let tokensSold = (await I_USDTieredSTO_Array[0].getTokensSold.call()).toNumber(); + let tokenDetails = await I_USDTieredSTO_Array[0].getSTODetails.call(); + assert.equal(tokensSold, tokenDetails[7].toNumber()); + }); }); describe("convertToUSD", async () => { @@ -4306,12 +4787,12 @@ contract("USDTieredSTO", accounts => { assert.equal((await I_USDTieredSTOFactory.getSetupCost.call()).toNumber(), STOSetupCost); assert.equal((await I_USDTieredSTOFactory.getTypes.call())[0], 3); assert.equal(web3.utils.hexToString(await I_USDTieredSTOFactory.getName.call()), "USDTieredSTO", "Wrong Module added"); - assert.equal(await I_USDTieredSTOFactory.description.call(), + assert.equal(await I_USDTieredSTOFactory.description.call(), "It allows both accredited and non-accredited investors to contribute into the STO. Non-accredited investors will be capped at a maximum investment limit (as a default or specific to their jurisdiction). Tokens will be sold according to tiers sequentially & each tier has its own price and volume of tokens to sell. Upon receipt of funds (ETH, POLY or DAI), security tokens will automatically transfer to investor’s wallet address", "Wrong Module added"); assert.equal(await I_USDTieredSTOFactory.title.call(), "USD Tiered STO", "Wrong Module added"); assert.equal(await I_USDTieredSTOFactory.getInstructions.call(), "Initialises a USD tiered STO.", "Wrong Module added"); - assert.equal(await I_USDTieredSTOFactory.version.call(), "1.0.0"); + assert.equal(await I_USDTieredSTOFactory.version.call(), "2.1.0"); let tags = await I_USDTieredSTOFactory.getTags.call(); assert.equal(web3.utils.hexToString(tags[0]), "USD"); assert.equal(web3.utils.hexToString(tags[1]), "Tiered"); diff --git a/test/q_usd_tiered_sto_sim.js b/test/q_usd_tiered_sto_sim.js index 0548a6db6..2b3062574 100644 --- a/test/q_usd_tiered_sto_sim.js +++ b/test/q_usd_tiered_sto_sim.js @@ -153,8 +153,8 @@ contract("USDTieredSTO Sim", accounts => { name: "_reserveWallet" }, { - type: "address", - name: "_usdToken" + type: "address[]", + name: "_usdTokens" } ] }; @@ -195,7 +195,7 @@ contract("USDTieredSTO Sim", accounts => { I_SecurityTokenRegistryProxy, I_STRProxied ] = instances; - + I_DaiToken = await PolyTokenFaucet.new({from: POLYMATH}); // STEP 5: Deploy the USDTieredSTOFactory @@ -285,7 +285,7 @@ contract("USDTieredSTO Sim", accounts => { _fundRaiseTypes[stoId], _wallet[stoId], _reserveWallet[stoId], - _usdToken[stoId] + [_usdToken[stoId]] ]; let bytesSTO = web3.eth.abi.encodeFunctionCall(functionSignature, config); @@ -299,22 +299,22 @@ contract("USDTieredSTO Sim", accounts => { assert.equal(await I_USDTieredSTO_Array[stoId].endTime.call(), _endTime[stoId], "Incorrect _endTime in config"); for (var i = 0; i < _ratePerTier[stoId].length; i++) { assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTier.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[0].toNumber(), _ratePerTier[stoId][i].toNumber(), "Incorrect _ratePerTier in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].ratePerTierDiscountPoly.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[1].toNumber(), _ratePerTierDiscountPoly[stoId][i].toNumber(), "Incorrect _ratePerTierDiscountPoly in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].toNumber(), _tokensPerTierTotal[stoId][i].toNumber(), "Incorrect _tokensPerTierTotal in config" ); assert.equal( - (await I_USDTieredSTO_Array[stoId].tokensPerTierDiscountPoly.call(i)).toNumber(), + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].toNumber(), _tokensPerTierDiscountPoly[stoId][i].toNumber(), "Incorrect _tokensPerTierDiscountPoly in config" ); @@ -455,13 +455,13 @@ contract("USDTieredSTO Sim", accounts => { let Tokens_discount = []; for (var i = 0; i < _ratePerTier[stoId].length; i++) { Tokens_total.push( - (await I_USDTieredSTO_Array[stoId].tokensPerTierTotal.call(i)).sub( - await I_USDTieredSTO_Array[stoId].mintedPerTierTotal.call(i) + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[2].sub( + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[4] ) ); Tokens_discount.push( - (await I_USDTieredSTO_Array[stoId].tokensPerTierDiscountPoly.call(i)).sub( - await I_USDTieredSTO_Array[stoId].mintedPerTierDiscountPoly.call(i) + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[3].sub( + (await I_USDTieredSTO_Array[stoId].tiers.call(i))[5] ) ); } @@ -598,7 +598,7 @@ contract("USDTieredSTO Sim", accounts => { await I_DaiToken.getTokens(investment_DAI, _investor); await I_DaiToken.approve(I_USDTieredSTO_Array[stoId].address, investment_DAI, { from: _investor }); await catchRevert( - I_USDTieredSTO_Array[stoId].buyWithUSD(_investor, investment_DAI, { from: _investor, gasPrice: GAS_PRICE }) + I_USDTieredSTO_Array[stoId].buyWithUSD(_investor, investment_DAI, I_DaiToken.address, { from: _investor, gasPrice: GAS_PRICE }) ); } else await catchRevert( @@ -681,7 +681,7 @@ contract("USDTieredSTO Sim", accounts => { .yellow ); } else if (isDai && investment_DAI.gt(10)) { - tx = await I_USDTieredSTO_Array[stoId].buyWithUSD(_investor, investment_DAI, { from: _investor, gasPrice: GAS_PRICE }); + tx = await I_USDTieredSTO_Array[stoId].buyWithUSD(_investor, investment_DAI, I_DaiToken.address, { from: _investor, gasPrice: GAS_PRICE }); gasCost = new BigNumber(GAS_PRICE).mul(tx.receipt.gasUsed); console.log( `buyWithUSD: ${investment_Token.div(10 ** 18)} tokens for ${investment_DAI.div(10 ** 18)} DAI by ${_investor}` diff --git a/test/r_concurrent_STO.js b/test/r_concurrent_STO.js index 7912658a4..3274e7fb8 100644 --- a/test/r_concurrent_STO.js +++ b/test/r_concurrent_STO.js @@ -2,7 +2,7 @@ import latestTime from "./helpers/latestTime"; import { duration, promisifyLogWatch, latestBlock } from "./helpers/utils"; import { takeSnapshot, increaseTime, revertToSnapshot } from "./helpers/time"; import { encodeProxyCall, encodeModuleCall } from "./helpers/encodeCall"; -import { +import { setUpPolymathNetwork, deployDummySTOAndVerifyed, deployCappedSTOAndVerifyed, @@ -94,7 +94,7 @@ contract("Concurrent STO", accounts => { I_SecurityTokenRegistryProxy, I_STRProxied ] = instances; - + // STEP 2: Deploy the STO Factories [I_CappedSTOFactory] = await deployCappedSTOAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, STOSetupCost); @@ -176,7 +176,7 @@ contract("Concurrent STO", accounts => { const startTime = latestTime() + duration.days(1); const endTime = latestTime() + duration.days(90); const cap = web3.utils.toWei("10000"); - const rate = 1000; + const rate = web3.utils.toWei("1000"); const fundRaiseType = [0]; const budget = 0; const maxCost = STOSetupCost; diff --git a/test/t_security_token_registry_proxy.js b/test/t_security_token_registry_proxy.js index 40b1cced8..30bacc759 100644 --- a/test/t_security_token_registry_proxy.js +++ b/test/t_security_token_registry_proxy.js @@ -125,12 +125,12 @@ contract("SecurityTokenRegistryProxy", accounts => { it("Verify the initialize data", async () => { assert.equal( - (await I_STRProxied.getUintValues.call(web3.utils.soliditySha3("expiryLimit"))).toNumber(), + (await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("expiryLimit"))).toNumber(), 60 * 24 * 60 * 60, "Should equal to 60 days" ); assert.equal( - (await I_STRProxied.getUintValues.call(web3.utils.soliditySha3("tickerRegFee"))).toNumber(), + (await I_STRProxied.getUintValue.call(web3.utils.soliditySha3("tickerRegFee"))).toNumber(), web3.utils.toWei("250") ); }); diff --git a/test/u_module_registry_proxy.js b/test/u_module_registry_proxy.js index 6d00b8fe2..546200943 100644 --- a/test/u_module_registry_proxy.js +++ b/test/u_module_registry_proxy.js @@ -9,6 +9,7 @@ const ModuleRegistryProxy = artifacts.require("./ModuleRegistryProxy.sol"); const ModuleRegistry = artifacts.require("./ModuleRegistry.sol"); const STFactory = artifacts.require("./STFactory.sol"); const SecurityToken = artifacts.require("./SecurityToken.sol"); +const GeneralTransferManagerLogic = artifacts.require("./GeneralTransferManager.sol"); const GeneralTransferManagerFactory = artifacts.require("./GeneralTransferManagerFactory.sol"); const GeneralPermissionManagerFactory = artifacts.require("./GeneralPermissionManagerFactory.sol"); @@ -122,7 +123,9 @@ contract("ModuleRegistryProxy", accounts => { await I_MRProxied.updateFromRegistry({ from: account_polymath }); // STEP 4: Deploy the GeneralTransferManagerFactory - I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, { + let I_GeneralTransferManagerLogic = await GeneralTransferManagerLogic.new("0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", { from: account_polymath }); + + I_GeneralTransferManagerFactory = await GeneralTransferManagerFactory.new(I_PolyToken.address, 0, 0, 0, I_GeneralTransferManagerLogic.address, { from: account_polymath }); @@ -150,11 +153,11 @@ contract("ModuleRegistryProxy", accounts => { it("Verify the initialize data", async () => { assert.equal( - await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("owner")), + await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("owner")), account_polymath, "Should equal to right address" ); - assert.equal(await I_MRProxied.getAddressValues.call(web3.utils.soliditySha3("polymathRegistry")), I_PolymathRegistry.address); + assert.equal(await I_MRProxied.getAddressValue.call(web3.utils.soliditySha3("polymathRegistry")), I_PolymathRegistry.address); }); }); diff --git a/test/v_tracked_redemptions.js b/test/v_tracked_redemptions.js index 159564651..2d5ae8c6c 100644 --- a/test/v_tracked_redemptions.js +++ b/test/v_tracked_redemptions.js @@ -102,7 +102,7 @@ contract("TrackedRedemption", accounts => { I_STRProxied ] = instances; - + // STEP 4: Deploy the TrackedRedemption [I_TrackedRedemptionFactory] = await deployRedemptionAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); [P_TrackedRedemptionFactory] = await deployRedemptionAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); diff --git a/test/w_lockup_transfer_manager.js b/test/w_lockup_transfer_manager.js new file mode 100644 index 000000000..41f9ea61e --- /dev/null +++ b/test/w_lockup_transfer_manager.js @@ -0,0 +1,999 @@ +import latestTime from './helpers/latestTime'; +import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall } from './helpers/encodeCall'; +import { setUpPolymathNetwork, deployLockupVolumeRTMAndVerified } from "./helpers/createInstances"; +import { catchRevert } from "./helpers/exceptions"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const LockUpTransferManager = artifacts.require('./LockUpTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('LockUpTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let P_LockUpTransferManagerFactory; + let I_SecurityTokenRegistryProxy; + let P_LockUpTransferManager; + let I_GeneralTransferManagerFactory; + let I_LockUpTransferManagerFactory; + let I_GeneralPermissionManager; + let I_LockUpTransferManager; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_MRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_PolymathRegistry; + let I_SecurityToken_div; + let I_GeneralTransferManager_div; + let I_LockUpVolumeRestrictionTM_div; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + + const name2 = "Core"; + const symbol2 = "Core"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + let temp; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + + account_investor1 = accounts[7]; + account_investor2 = accounts[8]; + account_investor3 = accounts[9]; + + let instances = await setUpPolymathNetwork(account_polymath, token_owner); + + [ + I_PolymathRegistry, + I_PolyToken, + I_FeatureRegistry, + I_ModuleRegistry, + I_ModuleRegistryProxy, + I_MRProxied, + I_GeneralTransferManagerFactory, + I_STFactory, + I_SecurityTokenRegistry, + I_SecurityTokenRegistryProxy, + I_STRProxied + ] = instances; + + // STEP 4(c): Deploy the LockUpVolumeRestrictionTMFactory + [I_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 4(d): Deploy the LockUpVolumeRestrictionTMFactory + [P_LockUpTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + + LockupVolumeRestrictionTransferManagerFactory: + ${I_LockUpTransferManagerFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + }); + + + it("Should register another ticker before the generation of new security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol2, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol2.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name2, symbol2, tokenDetails, true, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol2.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken_div = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken_div.ModuleAdded({from: _blockNo}), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken_div.getModulesByType(2))[0]; + I_GeneralTransferManager_div = GeneralTransferManager.at(moduleData); + }); + + + }); + + describe("Buy tokens using on-chain whitelist and test locking them up and attempting to transfer", async() => { + + it("Should Buy the tokens from non-divisible", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_SecurityToken.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should Buy the tokens from the divisible token", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager_div.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_SecurityToken_div.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken_div.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should Buy some more tokens from non-divisible tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('10', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }); + + it("Should unsuccessfully attach the LockUpTransferManager factory with the security token -- failed because Token is not paid", async () => { + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await catchRevert( + I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + ) + }); + + it("Should successfully attach the LockUpTransferManager factory with the security token", async () => { + let snapId = await takeSnapshot(); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); + const tx = await I_SecurityToken.addModule(P_LockUpTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[3].args._name) + .replace(/\u0000/g, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + P_LockUpTransferManager = LockUpTransferManager.at(tx.logs[3].args._module); + await revertToSnapshot(snapId); + }); + + it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the non-divisible security token", async () => { + const tx = await I_SecurityToken.addModule(I_LockUpTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + I_LockUpTransferManager = LockUpTransferManager.at(tx.logs[2].args._module); + }); + + it("Should successfully attach the LockUpVolumeRestrictionTMFactory with the divisible security token", async () => { + const tx = await I_SecurityToken_div.addModule(I_LockUpTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "LockUpVolumeRestrictionTMFactory doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "LockUpTransferManager", + "LockUpTransferManager module was not added" + ); + I_LockUpVolumeRestrictionTM_div = LockUpTransferManager.at(tx.logs[2].args._module); + }); + + it("Add a new token holder", async() => { + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor3, + latestTime(), + latestTime(), + latestTime() + duration.days(10), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Add the Investor in to the whitelist + // Mint some tokens + await I_SecurityToken.mint(account_investor3, web3.utils.toWei('10', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }); + + it("Should pause the tranfers at transferManager level", async() => { + let tx = await I_LockUpTransferManager.pause({from: token_owner}); + }); + + it("Should still be able to transfer between existing token holders up to limit", async() => { + // Transfer Some tokens between the investor + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('3', 'ether') + ); + }); + + it("Should unpause the tranfers at transferManager level", async() => { + await I_LockUpTransferManager.unpause({from: token_owner}); + }); + + it("Should prevent the creation of a lockup with bad parameters where the lockupAmount is zero", async() => { + // create a lockup + // this will generate an exception because the lockupAmount is zero + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + 0, + latestTime() + + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + "a_lockup", + { + from: token_owner + } + ) + ) + }); + + it("Should prevent the creation of a lockup with bad parameters where the releaseFrequencySeconds is zero", async() => { + // create a lockup + // this will generate an exception because the releaseFrequencySeconds is zero + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + 0, + "a_lockup", + { + from: token_owner + } + ) + ); + }); + + it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is zero", async() => { + // create a lockup + // this will generate an exception because the lockUpPeriodSeconds is zero + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + web3.utils.toWei('1', 'ether'), + latestTime() + duration.seconds(1), + 0, + duration.seconds(100000), + "a_lockup", + { + from: token_owner + } + ) + ); + }); + + it("Should add the lockup type -- fail because of bad owner", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpType( + web3.utils.toWei('12', 'ether'), + latestTime() + duration.days(1), + 60, + 20, + "a_lockup", + { + from: account_investor1 + } + ) + ); + }) + + it("Should add the new lockup type", async() => { + let tx = await I_LockUpTransferManager.addNewLockUpType( + web3.utils.toWei('12', 'ether'), + latestTime() + duration.days(1), + 60, + 20, + "a_lockup", + { + from: token_owner + } + ); + assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('12', 'ether')); + }); + + it("Should fail to add the creation of the lockup where lockupName is already exists", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUser( + account_investor1, + web3.utils.toWei('5', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + "a_lockup", + { + from: token_owner + } + ) + ); + }) + + it("Should allow the creation of a lockup where the lockup amount is divisible" , async() => { + // create a lockup + let tx = await I_LockUpVolumeRestrictionTM_div.addNewLockUpToUser( + account_investor1, + web3.utils.toWei('0.5', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + "a_lockup", + { + from: token_owner + } + ); + assert.equal(tx.logs[1].args._userAddress, account_investor1); + assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('0.5', 'ether')); + }); + + it("Should allow the creation of a lockup where the lockup amount is prime no", async() => { + // create a lockup + let tx = await I_LockUpVolumeRestrictionTM_div.addNewLockUpToUser( + account_investor1, + web3.utils.toWei('64951', 'ether'), + latestTime() + duration.seconds(1), + duration.seconds(400000), + duration.seconds(100000), + "b_lockup", + { + from: token_owner + } + ); + assert.equal(tx.logs[1].args._userAddress, account_investor1); + assert.equal((tx.logs[0].args._lockupAmount).toNumber(), web3.utils.toWei('64951', 'ether')); + }); + + it("Should prevent the transfer of tokens in a lockup", async() => { + + let balance = await I_SecurityToken.balanceOf(account_investor2) + console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + // create a lockup for their entire balance + // over 12 seconds total, with 3 periods of 20 seconds each. + await I_LockUpTransferManager.addNewLockUpToUser( + account_investor2, + balance, + latestTime() + duration.seconds(1), + 60, + 20, + "b_lockup", + { + from: token_owner + } + ); + await increaseTime(2); + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await catchRevert( + I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) + ); + }); + + it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { + // wait 20 seconds + await increaseTime(duration.seconds(20)); + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await catchRevert( + I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) + ); + }); + + it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); + }); + + it("Should again transfer of tokens in a lockup if a period has passed", async() => { + let tx = await I_LockUpTransferManager.getLockUp.call("b_lockup"); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); + }); + + it("Should allow the transfer of more tokens in a lockup if another period has passed", async() => { + + // wait 20 more seconds + 1 to get rid of same block time + await increaseTime(duration.seconds(21)); + let tx = await I_LockUpTransferManager.getLockUp.call( "b_lockup"); + console.log("Amount get unlocked:", (tx[4].toNumber())); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); + }); + + it("Buy more tokens from secondary market to investor2", async() => { + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('5', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('10', 'ether') + ); + }) + + it("Should allow transfer for the tokens that comes from secondary market + unlocked tokens", async() => { + + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('6', 'ether') + ); + }); + + it("Should allow the transfer of all tokens in a lockup if the entire lockup has passed", async() => { + + let balance = await I_SecurityToken.balanceOf(account_investor2) + + // wait 20 more seconds + 1 to get rid of same block time + await increaseTime(duration.seconds(21)); + console.log((await I_LockUpTransferManager.getLockedTokenToUser.call(account_investor2)).toNumber()); + await I_SecurityToken.transfer(account_investor1, balance, { from: account_investor2 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('0', 'ether') + ); + }); + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ) + ); + }) + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3, account_investor3], + [], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ) + ); + }) + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45, 50], + [20, 15], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ) + ); + }) + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45, 50], + [20, 15, 10], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ) + ); + }) + + it("Should fail to add the multiple lockups -- because array length mismatch", async() => { + await catchRevert( + I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + ["c_lockup"], + { + from: token_owner + } + ) + ); + }); + + it("Should add the multiple lockup to a address", async() => { + await I_LockUpTransferManager.addNewLockUpToUserMulti( + [account_investor3, account_investor3], + [web3.utils.toWei("6", "ether"), web3.utils.toWei("3", "ether")], + [latestTime() + duration.seconds(1), latestTime() + duration.seconds(21)], + [60, 45], + [20, 15], + ["c_lockup", "d_lockup"], + { + from: token_owner + } + ); + + await increaseTime(1); + let tx = await I_LockUpTransferManager.getLockUp.call("c_lockup"); + let tx2 = await I_LockUpTransferManager.getLockUp.call("d_lockup"); + console.log("Total Amount get unlocked:", (tx[4].toNumber()) + (tx2[4].toNumber())); + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor3 }) + ); + + }); + + it("Should transfer the tokens after period get passed", async() => { + // increase 20 sec that makes 1 period passed + // 2 from a period and 1 is already unlocked + await increaseTime(21); + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + }) + + it("Should transfer the tokens after passing another period of the lockup", async() => { + // increase the 15 sec that makes first period of another lockup get passed + // allow 1 token to transfer + await increaseTime(15); + // first fail because 3 tokens are not in unlocked state + await catchRevert( + I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + ) + // second txn will pass because 1 token is in unlocked state + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1'), { from: account_investor3 }) + }); + + it("Should transfer the tokens from both the lockup simultaneously", async() => { + // Increase the 20 sec (+ 1 to mitigate the block time) that unlocked another 2 tokens from the lockup 1 and simultaneously unlocked 1 + // more token from the lockup 2 + await increaseTime(21); + + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('3', 'ether') + ); + }); + + it("Should remove multiple lockup --failed because of bad owner", async() => { + await catchRevert( + I_LockUpTransferManager.removeLockUpFromUserMulti( + [account_investor3, account_investor3], + ["c_lockup", "d_lockup"], + { + from: account_polymath + } + ) + ); + }); + + it("Should remove the multiple lockup -- failed because of invalid lockupname", async() => { + await catchRevert( + I_LockUpTransferManager.removeLockUpFromUserMulti( + [account_investor3, account_investor3], + ["c_lockup", "e_lockup"], + { + from: account_polymath + } + ) + ); + }) + + it("Should remove the multiple lockup", async() => { + await I_LockUpTransferManager.removeLockUpFromUserMulti( + [account_investor3, account_investor3], + ["d_lockup", "c_lockup"], + { + from: token_owner + } + ) + // do the free transaction now + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3'), { from: account_investor3 }) + }); + + it("Should fail to modify the lockup -- because of bad owner", async() => { + await I_SecurityToken.mint(account_investor3, web3.utils.toWei("9"), {from: token_owner}); + + let tx = await I_LockUpTransferManager.addNewLockUpToUser( + account_investor3, + web3.utils.toWei("9"), + latestTime() + duration.minutes(5), + 60, + 20, + "z_lockup", + { + from: token_owner + } + ); + + await catchRevert( + // edit the lockup + I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() + duration.seconds(1), + 60, + 20, + "z_lockup", + { + from: account_polymath + } + ) + ) + }) + + it("Modify the lockup when startTime is in past -- failed because startTime is in the past", async() => { + await catchRevert( + // edit the lockup + I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() - duration.seconds(50), + 60, + 20, + "z_lockup", + { + from: token_owner + } + ) + ) + }) + + it("Modify the lockup when startTime is in past -- failed because of invalid index", async() => { + await catchRevert( + // edit the lockup + I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() + duration.seconds(50), + 60, + 20, + "m_lockup", + { + from: token_owner + } + ) + ) + }) + + it("should successfully modify the lockup", async() => { + // edit the lockup + await I_LockUpTransferManager.modifyLockUpType( + web3.utils.toWei("9"), + latestTime() + duration.seconds(50), + 60, + 20, + "z_lockup", + { + from: token_owner + } + ); + }) + + it("Should prevent the transfer of tokens in an edited lockup", async() => { + + // balance here should be 12000000000000000000 (12e18 or 12 eth) + let balance = await I_SecurityToken.balanceOf(account_investor1) + + console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + + // create a lockup for their entire balance + // over 16 seconds total, with 4 periods of 4 seconds each. + await I_LockUpTransferManager.addNewLockUpToUser( + account_investor1, + balance, + latestTime() + duration.minutes(5), + 60, + 20, + "f_lockup", + { + from: token_owner + } + ); + + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) + ); + + let lockUp = await I_LockUpTransferManager.getLockUp("f_lockup"); + console.log(lockUp); + // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount + assert.equal( + lockUp[0].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber(), + balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber() + ); + assert.equal(lockUp[2].toNumber(), 60); + assert.equal(lockUp[3].toNumber(), 20); + assert.equal(lockUp[4].toNumber(), 0); + + // edit the lockup + temp = latestTime() + duration.seconds(1); + await I_LockUpTransferManager.modifyLockUpType( + balance, + temp, + 60, + 20, + "f_lockup", + { + from: token_owner + } + ); + + // attempt a transfer + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) + ); + + // wait 20 seconds + await increaseTime(21); + + // transfer should succeed + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }); + + }); + + it("Modify the lockup during the lockup periods", async() => { + let balance = await I_SecurityToken.balanceOf(account_investor1) + let lockUp = await I_LockUpTransferManager.getLockUp("f_lockup"); + console.log(lockUp[4].dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); + // edit the lockup + await I_LockUpTransferManager.modifyLockUpType( + balance, + latestTime() + duration.days(10), + 90, + 30, + "f_lockup", + { + from: token_owner + } + ); + }); + + it("Should remove the lockup multi", async() => { + await I_LockUpTransferManager.addNewLockUpTypeMulti( + [web3.utils.toWei("10"), web3.utils.toWei("16")], + [latestTime() + duration.days(1), latestTime() + duration.days(1)], + [50000, 50000], + [10000, 10000], + ["k_lockup", "l_lockup"], + { + from: token_owner + } + ); + + // removing the lockup type + let tx = await I_LockUpTransferManager.removeLockupType("k_lockup", {from: token_owner}); + assert.equal(web3.utils.toUtf8(tx.logs[0].args._lockupName), "k_lockup"); + + // attaching the lockup to a user + + await I_LockUpTransferManager.addLockUpByName(account_investor2, "l_lockup", {from: token_owner}); + + // try to delete the lockup but fail + + await catchRevert( + I_LockUpTransferManager.removeLockupType("l_lockup", {from: token_owner}) + ); + }) + + it("Should get the data of all lockups", async() => { + console.log(await I_LockUpTransferManager.getAllLockupData.call()); + }); + + it("Should succesfully get the non existed lockup value, it will give everything 0", async() => { + let data = await I_LockUpTransferManager.getLockUp(9); + assert.equal(data[0], 0); + }) + + it("Should get configuration function signature", async() => { + let sig = await I_LockUpTransferManager.getInitFunction.call(); + assert.equal(web3.utils.hexToNumber(sig), 0); + }); + + it("Should get the all lockups added by the issuer till now", async() => { + let tx = await I_LockUpTransferManager.getAllLockups.call(); + for (let i = 0; i < tx.length; i++) { + console.log(web3.utils.toUtf8(tx[i])); + } + }) + + it("Should get the permission", async() => { + let perm = await I_LockUpTransferManager.getPermissions.call(); + assert.equal(perm.length, 1); + assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ''), "ADMIN") + }); + + }); + + describe("LockUpTransferManager Transfer Manager Factory test cases", async() => { + + it("Should get the exact details of the factory", async() => { + assert.equal(await I_LockUpTransferManagerFactory.getSetupCost.call(),0); + assert.equal((await I_LockUpTransferManagerFactory.getTypes.call())[0],2); + assert.equal(web3.utils.toAscii(await I_LockUpTransferManagerFactory.getName.call()) + .replace(/\u0000/g, ''), + "LockUpTransferManager", + "Wrong Module added"); + assert.equal(await I_LockUpTransferManagerFactory.description.call(), + "Manage transfers using lock ups over time", + "Wrong Module added"); + assert.equal(await I_LockUpTransferManagerFactory.title.call(), + "LockUp Transfer Manager", + "Wrong Module added"); + assert.equal(await I_LockUpTransferManagerFactory.getInstructions.call(), + "Allows an issuer to set lockup periods for user addresses, with funds distributed over time. Init function takes no parameters.", + "Wrong Module added"); + assert.equal(await I_LockUpTransferManagerFactory.version.call(), "1.0.0"); + }); + + it("Should get the tags of the factory", async() => { + let tags = await I_LockUpTransferManagerFactory.getTags.call(); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "LockUp"); + }); + }); + +}); diff --git a/test/w_lockup_volume_restriction_transfer_manager.js b/test/w_lockup_volume_restriction_transfer_manager.js deleted file mode 100644 index fe06f3f5c..000000000 --- a/test/w_lockup_volume_restriction_transfer_manager.js +++ /dev/null @@ -1,808 +0,0 @@ -import latestTime from './helpers/latestTime'; -import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; -import { encodeProxyCall } from './helpers/encodeCall'; -import { setUpPolymathNetwork, deployLockupVolumeRTMAndVerified } from "./helpers/createInstances"; -import { catchRevert } from "./helpers/exceptions"; - -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const VolumeRestrictionTransferManager = artifacts.require('./LockupVolumeRestrictionTM'); -const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); - -const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port - -contract('LockupVolumeRestrictionTransferManager', accounts => { - - // Accounts Variable declaration - let account_polymath; - let account_issuer; - let token_owner; - let account_investor1; - let account_investor2; - let account_investor3; - let account_investor4; - - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - - let message = "Transaction Should Fail!"; - - // Contract Instance Declaration - let P_VolumeRestrictionTransferManagerFactory; - let I_SecurityTokenRegistryProxy; - let P_VolumeRestrictionTransferManager; - let I_GeneralTransferManagerFactory; - let I_VolumeRestrictionTransferManagerFactory; - let I_GeneralPermissionManager; - let I_VolumeRestrictionTransferManager; - let I_GeneralTransferManager; - let I_ModuleRegistryProxy; - let I_ModuleRegistry; - let I_FeatureRegistry; - let I_SecurityTokenRegistry; - let I_STRProxied; - let I_MRProxied; - let I_STFactory; - let I_SecurityToken; - let I_PolyToken; - let I_PolymathRegistry; - - // SecurityToken Details - const name = "Team"; - const symbol = "sap"; - const tokenDetails = "This is equity type of issuance"; - const decimals = 18; - const contact = "team@polymath.network"; - - // Module key - const delegateManagerKey = 1; - const transferManagerKey = 2; - const stoKey = 3; - - // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); - - before(async() => { - // Accounts setup - account_polymath = accounts[0]; - account_issuer = accounts[1]; - - token_owner = account_issuer; - - account_investor1 = accounts[7]; - account_investor2 = accounts[8]; - account_investor3 = accounts[9]; - - let instances = await setUpPolymathNetwork(account_polymath, token_owner); - - [ - I_PolymathRegistry, - I_PolyToken, - I_FeatureRegistry, - I_ModuleRegistry, - I_ModuleRegistryProxy, - I_MRProxied, - I_GeneralTransferManagerFactory, - I_STFactory, - I_SecurityTokenRegistry, - I_SecurityTokenRegistryProxy, - I_STRProxied - ] = instances; - - // STEP 4(c): Deploy the VolumeRestrictionTransferManager - [I_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); - // STEP 4(d): Deploy the VolumeRestrictionTransferManager - [P_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); - - // Printing all the contract addresses - console.log(` - --------------------- Polymath Network Smart Contracts: --------------------- - PolymathRegistry: ${I_PolymathRegistry.address} - SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} - SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} - ModuleRegistry: ${I_ModuleRegistry.address} - ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} - FeatureRegistry: ${I_FeatureRegistry.address} - - STFactory: ${I_STFactory.address} - GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} - - LockupVolumeRestrictionTransferManagerFactory: - ${I_VolumeRestrictionTransferManagerFactory.address} - ----------------------------------------------------------------------------- - `); - }); - - describe("Generate the SecurityToken", async() => { - - it("Should register the ticker before the generation of the security token", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); - assert.equal(tx.logs[0].args._owner, token_owner); - assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); - }); - - it("Should generate the new security token with the same symbol as registered above", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); - - // Verify the successful generation of the security token - assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); - - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); - - // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); - assert.equal( - web3.utils.toAscii(log.args._name) - .replace(/\u0000/g, ''), - "GeneralTransferManager" - ); - }); - - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); - }); - - }); - - describe("Buy tokens using on-chain whitelist and test locking them up and attempting to transfer", async() => { - - it("Should Buy the tokens", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Jump time - await increaseTime(5000); - - // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei('2', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('2', 'ether') - ); - }); - - it("Should Buy some more tokens", async() => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei('10', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), - web3.utils.toWei('10', 'ether') - ); - }); - - it("Should unsuccessfully attach the VolumeRestrictionTransferManager factory with the security token -- failed because Token is not paid", async () => { - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); - await catchRevert( - I_SecurityToken.addModule(P_VolumeRestrictionTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }) - ) - }); - - it("Should successfully attach the VolumeRestrictionTransferManager factory with the security token", async () => { - let snapId = await takeSnapshot(); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); - const tx = await I_SecurityToken.addModule(P_VolumeRestrictionTransferManagerFactory.address, 0, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); - assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "VolumeRestrictionTransferManagerFactory doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[3].args._name) - .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "VolumeRestrictionTransferManagerFactory module was not added" - ); - P_VolumeRestrictionTransferManager = VolumeRestrictionTransferManager.at(tx.logs[3].args._module); - await revertToSnapshot(snapId); - }); - - it("Should successfully attach the VolumeRestrictionTransferManager with the security token", async () => { - const tx = await I_SecurityToken.addModule(I_VolumeRestrictionTransferManagerFactory.address, 0, 0, 0, { from: token_owner }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "VolumeRestrictionTransferManager doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) - .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "VolumeRestrictionTransferManager module was not added" - ); - I_VolumeRestrictionTransferManager = VolumeRestrictionTransferManager.at(tx.logs[2].args._module); - }); - - it("Add a new token holder", async() => { - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, - { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Add the Investor in to the whitelist - // Mint some tokens - await I_SecurityToken.mint(account_investor3, web3.utils.toWei('10', 'ether'), { from: token_owner }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), - web3.utils.toWei('10', 'ether') - ); - }); - - it("Should pause the tranfers at transferManager level", async() => { - let tx = await I_VolumeRestrictionTransferManager.pause({from: token_owner}); - }); - - it("Should still be able to transfer between existing token holders up to limit", async() => { - // Add the Investor in to the whitelist - // Mint some tokens - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('3', 'ether') - ); - }); - - it("Should unpause the tranfers at transferManager level", async() => { - await I_VolumeRestrictionTransferManager.unpause({from: token_owner}); - }); - - it("Should prevent the creation of a lockup with bad parameters where the totalAmount is zero", async() => { - // create a lockup - // this will generate an exception because the totalAmount is zero - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, 0, { from: token_owner }) - ) - }); - - it("Should prevent the creation of a lockup with bad parameters where the releaseFrequencySeconds is zero", async() => { - // create a lockup - // this will generate an exception because the releaseFrequencySeconds is zero - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 0, 0, web3.utils.toWei('1', 'ether'), { from: token_owner }) - ); - }); - - it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is zero", async() => { - // create a lockup - // this will generate an exception because the lockUpPeriodSeconds is zero - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 0, 4, 0, web3.utils.toWei('1', 'ether'), { from: token_owner }) - ); - }); - - - it("Should prevent the creation of a lockup with bad parameters where the total amount to be released is more granular than allowed by the token", async() => { - // create a lockup - // this will generate an exception because we're locking up 5e17 tokens but the granularity is 5e18 tokens - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, web3.utils.toWei('0.5', 'ether'), { from: token_owner }) - ); - }); - - it("Should prevent the creation of a lockup with bad parameters where the lockUpPeriodSeconds is not evenly divisible by releaseFrequencySeconds", async() => { - - // balance should be 9000000000000000000 here (9 eth) - let balance = await I_SecurityToken.balanceOf(account_investor2) - - - // create a lockup - // over 17 seconds total, with 4 periods. - // this will generate an exception because 17 is not evenly divisble by 4. - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 17, 4, 0, balance, { from: token_owner }) - ); - }); - - it("Should prevent the creation of a lockup with bad parameters where the total amount being locked up isn't evenly divisible by the number of total periods", async() => { - - // create a lockup for a balance of 1 eth - // over 16e18 seconds total, with 4e18 periods of 4 seconds each. - // this will generate an exception because 16e18 / 4e18 = 4e18 but the token granularity is 1e18 and 1e18 % 4e18 != 0 - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, web3.utils.toWei('16', 'ether'), 4, 0, web3.utils.toWei('1', 'ether'), { from: token_owner }) - ); - }); - - it("Should prevent the creation of a lockup with bad parameters where the amount to be released per period is too granular for the token", async() => { - - // balance should be 9000000000000000000 here (9 eth) - let balance = await I_SecurityToken.balanceOf(account_investor2) - - // create a lockup for their entire balance - // over 16 seconds total, with 4 periods of 4 seconds each. - // this will generate an exception because 9000000000000000000 / 4 = 2250000000000000000 but the token granularity is 1000000000000000000 - await catchRevert( - I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, balance, { from: token_owner }) - ); - - }); - - it("Should prevent the transfer of tokens in a lockup", async() => { - - let balance = await I_SecurityToken.balanceOf(account_investor2) - console.log("balance", balance.dividedBy(new BigNumber(1).times(new BigNumber(10).pow(18))).toNumber()); - // create a lockup for their entire balance - // over 12 seconds total, with 3 periods of 4 seconds each. - await I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 12, 4, 0, balance, { from: token_owner }); - - // read only - check if transfer will pass. it should return INVALID - let result = await I_VolumeRestrictionTransferManager.verifyTransfer.call(account_investor2, account_investor1, web3.utils.toWei('1', 'ether'), 0, false) - // enum Result {INVALID, NA, VALID, FORCE_VALID} and we want VALID so it should be 2 - assert.equal(result.toString(), '0') - - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - }); - - it("Should allow the transfer of tokens in a lockup if a period has passed", async() => { - - // wait 4 seconds - await increaseTime(duration.seconds(4)); - - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3', 'ether'), { from: account_investor2 }); - }); - - it("Should prevent the transfer of tokens if the amount is larger than the amount allowed by lockups", async() => { - - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 }) - ); - }); - - it("Should allow the transfer of more tokens in a lockup if another period has passed", async() => { - - // wait 4 more seconds - await increaseTime(4000); - - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3', 'ether'), { from: account_investor2 }); - }); - - it("Should allow the transfer of all tokens in a lockup if the entire lockup has passed", async() => { - - let balance = await I_SecurityToken.balanceOf(account_investor2) - - // wait 4 more seconds - await increaseTime(4000); - - await I_SecurityToken.transfer(account_investor1, balance, { from: account_investor2 }); - }); - - it("Should prevent the transfer of tokens in an edited lockup", async() => { - - - // balance here should be 12000000000000000000 (12e18 or 12 eth) - let balance = await I_SecurityToken.balanceOf(account_investor1) - - // create a lockup for their entire balance - // over 16 seconds total, with 4 periods of 4 seconds each. - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 16, 4, 0, balance, { from: token_owner }); - - // let blockNumber = await web3.eth.getBlockNumber(); - // console.log('blockNumber',blockNumber) - let now = (await web3.eth.getBlock('latest')).timestamp - - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) - ); - - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 1) - - let lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor1, 0); - // console.log(lockUp); - // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount - assert.equal(lockUp[0].toString(), '16'); - assert.equal(lockUp[1].toString(), '4'); - assert.equal(lockUp[2].toNumber(), now); - assert.equal(lockUp[3].toString(), balance.toString()); - - // edit the lockup - await I_VolumeRestrictionTransferManager.modifyLockUp(account_investor1, 0, 8, 4, 0, balance, { from: token_owner }); - - // attempt a transfer - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) - ); - - // wait 4 seconds - await new Promise(resolve => setTimeout(resolve, 4000)); - - // transfer should succeed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('6', 'ether'), { from: account_investor1 }); - - }); - - it("Should succesfully modify the lockup - fail because array index out of bound", async() => { - // balance here should be 12000000000000000000 (12e18 or 12 eth) - let balance = await I_SecurityToken.balanceOf(account_investor1); - await catchRevert( - I_VolumeRestrictionTransferManager.modifyLockUp(account_investor1, 8, 8, 4, 0, balance, { from: token_owner }) - ); - }) - - it("Should succesfully get the lockup - fail because array index out of bound", async() => { - await catchRevert( - I_VolumeRestrictionTransferManager.getLockUp(account_investor1, 9) - ); - }) - - it("Should be possible to remove a lockup -- couldn't transfer because of lock up", async() => { - - let acct1Balance = await I_SecurityToken.balanceOf(account_investor1) - - await catchRevert( - I_SecurityToken.transfer(account_investor2, acct1Balance, { from: account_investor1 }) - ); - - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 1) - - // remove the lockup - await I_VolumeRestrictionTransferManager.removeLockUp(account_investor1, 0, { from: token_owner }); - - lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount, 0) - - let acct2BalanceBefore = await I_SecurityToken.balanceOf(account_investor2) - await I_SecurityToken.transfer(account_investor2, acct1Balance, { from: account_investor1 }); - let acct2BalanceAfter = await I_SecurityToken.balanceOf(account_investor2) - - assert.equal(acct2BalanceAfter.sub(acct2BalanceBefore).toString(), acct1Balance.toString()) - }); - - it("Should try to remove the lockup --failed because of index is out of bounds", async() => { - await catchRevert( - I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 7, { from: token_owner }) - ); - }) - - it("Should be possible to create multiple lockups at once", async() => { - - let balancesBefore = {} - - // should be 12000000000000000000 - balancesBefore[account_investor2] = await I_SecurityToken.balanceOf(account_investor2) - - - // should be 10000000000000000000 - balancesBefore[account_investor3] = await I_SecurityToken.balanceOf(account_investor3) - - - let lockUpCountsBefore = {} - - // get lockups for acct 2 - lockUpCountsBefore[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCountsBefore[account_investor2], 1) // there's one old, expired lockup on acct already - - // get lockups for acct 3 - lockUpCountsBefore[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3); - assert.equal(lockUpCountsBefore[account_investor3], 0) - - // create lockups for their entire balances - await I_VolumeRestrictionTransferManager.addLockUpMulti( - [account_investor2, account_investor3], - [24, 8], - [4, 4], - [0, 0], - [balancesBefore[account_investor2], balancesBefore[account_investor3]], - { from: token_owner } - ); - - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }) - ); - - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor3 }) - ); - - let balancesAfter = {} - balancesAfter[account_investor2] = await I_SecurityToken.balanceOf(account_investor2) - assert.equal(balancesBefore[account_investor2].toString(), balancesAfter[account_investor2].toString()) - - balancesAfter[account_investor3] = await I_SecurityToken.balanceOf(account_investor3) - assert.equal(balancesBefore[account_investor3].toString(), balancesAfter[account_investor3].toString()) - - let lockUpCountsAfter = {} - - // get lockups for acct 2 - lockUpCountsAfter[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCountsAfter[account_investor2], 2); - - // get lockups for acct 3 - lockUpCountsAfter[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3); - assert.equal(lockUpCountsAfter[account_investor3], 1); - - // wait 4 seconds - await increaseTime(4000); - - // try transfers again - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 }); - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor3 }); - - - balancesAfter[account_investor2] = await I_SecurityToken.balanceOf(account_investor2) - assert.equal(balancesBefore[account_investor2].sub(web3.utils.toWei('2', 'ether')).toString(), balancesAfter[account_investor2].toString()) - - balancesAfter[account_investor3] = await I_SecurityToken.balanceOf(account_investor3) - assert.equal(balancesBefore[account_investor3].sub(web3.utils.toWei('5', 'ether')).toString(), balancesAfter[account_investor3].toString()) - - }); - - it("Should revert if the parameters are bad when creating multiple lockups", async() => { - - await catchRevert( - // pass in the wrong number of params. txn should revert - I_VolumeRestrictionTransferManager.addLockUpMulti( - [account_investor2, account_investor3], - [16, 8], - [2], // this array should have 2 elements but it has 1, which should cause a revert - [0, 0], - [web3.utils.toWei('1', 'ether'), web3.utils.toWei('1', 'ether')], - { from: token_owner } - ) - ); - }); - - it("Should be possible to create a lockup with a specific start time in the future", async() => { - - // remove all lockups for account 2 - let lockUpsLength = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpsLength, 2); - await I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 0, { from: token_owner }); - await I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 0, { from: token_owner }); - lockUpsLength = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpsLength, 0); - - let now = latestTime(); - - // balance here should be 10000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor2) - - await I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 100, 10, now + duration.seconds(4), balance, { from: token_owner }); - - // wait 4 seconds for the lockup to begin - await increaseTime(duration.seconds(4)); - - // try another transfer. it should also fail because the lockup has just begun - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - - }); - - it("Should be possible to edit a lockup with a specific start time in the future", async() => { - - // edit the lockup - let now = latestTime(); - - // should be 10000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor2) - - // check and get the lockup - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCount, 1) - - let lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - - // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount - assert.equal(lockUp[0].toString(), '100'); - assert.equal(lockUp[1].toString(), '10'); - assert.isAtMost(lockUp[2].toNumber(), now); - assert.equal(lockUp[3].toString(), balance.toString()); - - // edit the lockup - await I_VolumeRestrictionTransferManager.modifyLockUp(account_investor2, 0, 8, 4, now + duration.seconds(4), balance, { from: token_owner }); - - // check and get the lockup again - lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2); - assert.equal(lockUpCount, 1) - - lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - - // elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount - assert.equal(lockUp[0].toString(), '8'); - assert.equal(lockUp[1].toString(), '4'); - assert.isAtMost(lockUp[2].toNumber(), now + 4); - assert.equal(lockUp[3].toString(), balance.toString()); - - // try a transfer. it should fail because again, the lockup hasn't started yet. - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - - // wait 4 seconds for the lockup to begin - await increaseTime(duration.seconds(4)); - - // try another transfer. it should fail because the lockup has just begun - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - - // wait 4 seconds for the lockup's first period to elapse - await increaseTime(duration.seconds(4)); - - // try another transfer. it should pass - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 }); - - - // try another transfer without waiting for another period to pass. it should fail - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 }) - ); - - // wait 4 seconds for the lockup's first period to elapse - await increaseTime(duration.seconds(4)); - - let lockUpBeforeVerify = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - // check if transfer will pass in read-only operation - let result = await I_VolumeRestrictionTransferManager.verifyTransfer.call(account_investor2, account_investor1, web3.utils.toWei('5', 'ether'), 0, false) - // enum Result {INVALID, NA, VALID, FORCE_VALID} and we want VALID so it should be 2 - assert.equal(result.toString(), '2') - let lockUpAfterVerify = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0); - - assert.equal(lockUpBeforeVerify[4].toString(), lockUpAfterVerify[4].toString()) - - // try another transfer. it should pass - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 }); - - // wait 4 seconds for the lockup's first period to elapse. but, we are all out of periods. - await increaseTime(duration.seconds(4)); - - // try one final transfer. this should fail because the user has already withdrawn their entire balance - await catchRevert( - I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor2 }) - ); - }); - - it("Should be possible to stack lockups", async() => { - // should be 17000000000000000000 - let balance = await I_SecurityToken.balanceOf(account_investor1) - - // check and make sure that acct1 has no lockups so far - let lockUpCount = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor1); - assert.equal(lockUpCount.toString(), 0) - - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 12, 4, 0, web3.utils.toWei('6', 'ether'), { from: token_owner }); - - // try to transfer 11 tokens that aren't locked up yet be locked up. should succeed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('11', 'ether'), { from: account_investor1 }); - - // try a transfer. it should fail because it's locked up from the first lockups - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) - ); - // wait 4 seconds for the lockup's first period to elapse. - await increaseTime(duration.seconds(4)); - - // should succeed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('2', 'ether'), { from: account_investor1 }); - - // send 8 back to investor1 so that we can lock them up - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('8', 'ether'), { from: account_investor2 }); - - // let's add another lockup to stack them - await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 16, 4, 0, web3.utils.toWei('8', 'ether'), { from: token_owner }); - - // try a transfer. it should fail because it's locked up from both lockups - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) - ); - - // wait 4 seconds for the 1st lockup's second period to elapse, and the 2nd lockup's first period to elapse - await increaseTime(duration.seconds(4)); - - // should now be able to transfer 4, because of 2 allowed from the 1st lockup and 2 from the 2nd - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('4', 'ether'), { from: account_investor1 }); - - // try aother transfer. it should fail because it's locked up from both lockups again - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }) - ); - - // wait 4 seconds for the 1st lockup's final period to elapse, and the 2nd lockup's second period to elapse - await increaseTime(duration.seconds(4)); - // should now be able to transfer 4, because of 2 allowed from the 1st lockup and 2 from the 2nd - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('4', 'ether'), { from: account_investor1 }); - - // wait 8 seconds for 2nd lockup's third and fourth periods to elapse - await increaseTime(duration.seconds(8)); - // should now be able to transfer 4, because there are 2 allowed per period in the 2nd lockup, and 2 periods have elapsed - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('4', 'ether'), { from: account_investor1 }); - - // send the 3 back from acct2 that we sent over in the beginning of this test - await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('3', 'ether'), { from: account_investor2 }); - - // try another transfer. it should pass because both lockups have been entirely used - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); - - balance = await I_SecurityToken.balanceOf(account_investor1) - assert.equal(balance.toString(), web3.utils.toWei('2', 'ether')) - }); - - - it("Should get configuration function signature", async() => { - let sig = await I_VolumeRestrictionTransferManager.getInitFunction.call(); - assert.equal(web3.utils.hexToNumber(sig), 0); - }); - - - it("Should get the permission", async() => { - let perm = await I_VolumeRestrictionTransferManager.getPermissions.call(); - assert.equal(perm.length, 1); - // console.log(web3.utils.toAscii(perm[0]).replace(/\u0000/g, '')) - assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ''), "ADMIN") - }); - - }); - - describe("VolumeRestriction Transfer Manager Factory test cases", async() => { - - it("Should get the exact details of the factory", async() => { - assert.equal(await I_VolumeRestrictionTransferManagerFactory.getSetupCost.call(),0); - assert.equal((await I_VolumeRestrictionTransferManagerFactory.getTypes.call())[0],2); - assert.equal(web3.utils.toAscii(await I_VolumeRestrictionTransferManagerFactory.getName.call()) - .replace(/\u0000/g, ''), - "LockupVolumeRestrictionTM", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.description.call(), - "Manage transfers using lock ups over time", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.title.call(), - "Lockup Volume Restriction Transfer Manager", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.getInstructions.call(), - "Allows an issuer to set lockup periods for user addresses, with funds distributed over time. Init function takes no parameters.", - "Wrong Module added"); - assert.equal(await I_VolumeRestrictionTransferManagerFactory.version.call(), "1.0.0"); - }); - - it("Should get the tags of the factory", async() => { - let tags = await I_VolumeRestrictionTransferManagerFactory.getTags.call(); - assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''), "Volume"); - }); - }); - -}); diff --git a/test/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/x_single_trade_volume_restriction.js b/test/x_single_trade_volume_restriction.js deleted file mode 100644 index 15c01e68c..000000000 --- a/test/x_single_trade_volume_restriction.js +++ /dev/null @@ -1,726 +0,0 @@ -import latestTime from './helpers/latestTime'; -import { duration, promisifyLogWatch, latestBlock } from './helpers/utils'; -import { takeSnapshot, increaseTime, revertToSnapshot } from './helpers/time'; -import { encodeModuleCall } from './helpers/encodeCall'; -import {deploySingleTradeVolumeRMAndVerified, setUpPolymathNetwork } from "./helpers/createInstances"; -import { catchRevert } from "./helpers/exceptions"; - -const SecurityToken = artifacts.require('./SecurityToken.sol'); -const GeneralPermissionManagerFactory = artifacts.require('./GeneralPermissionManagerFactory.sol'); -const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); -const SingleTradeVolumeRestrictionManager = artifacts.require('./SingleTradeVolumeRestrictionTM'); -const CountTransferManagerFactory = artifacts.require('./CountTransferManagerFactory.sol'); -const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); - -const Web3 = require('web3'); -const BigNumber = require('bignumber.js'); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port - -contract('SingleTradeVolumeRestrictionManager', accounts => { - - - - // Accounts Variable declaration - let account_polymath; - let account_issuer; - let token_owner; - let account_investor1; - let account_investor2; - let account_investor3; - let account_investor4; - let account_investor5; - let zero_address = '0x0000000000000000000000000000000000000000'; - - // investor Details - let fromTime = latestTime(); - let toTime = latestTime(); - let expiryTime = toTime + duration.days(15); - - let message = "Transaction Should Fail!"; - - // Contract Instance Declaration - let I_SecurityTokenRegistryProxy - let I_GeneralTransferManagerFactory; - let I_GeneralPermissionManager; - let I_GeneralTransferManager; - let I_SingleTradeVolumeRestrictionManagerFactory; - let I_SingleTradeVolumeRestrictionManager; - let P_SingleTradeVolumeRestrictionManagerFactory; - let P_SingleTradeVolumeRestrictionManager; - let I_SingleTradeVolumeRestrictionPercentageManager; - let I_ModuleRegistry; - let I_MRProxied; - let I_ModuleRegistryProxy; - let I_FeatureRegistry; - let I_SecurityTokenRegistry; - let I_STRProxied; - let I_STFactory; - let I_SecurityToken; - let I_PolyToken; - let I_PolymathRegistry; - - // SecurityToken Details - const name = "Team"; - const symbol = "sap"; - const tokenDetails = "This is equity type of issuance"; - const decimals = 18; - const contact = "team@polymath.network"; - const STVRParameters = ["bool", "uint256", "bool"]; - - // Module key - const delegateManagerKey = 1; - const transferManagerKey = 2; - const stoKey = 3; - - // Initial fee for ticker registry and security token registry - const initRegFee = web3.utils.toWei("250"); - - before(async () => { - // Accounts setup - account_polymath = accounts[0]; - account_issuer = accounts[1]; - - token_owner = account_issuer; - - account_investor1 = accounts[6]; - account_investor2 = accounts[7]; - account_investor3 = accounts[8]; - account_investor4 = accounts[9]; - account_investor5 = accounts[5]; - - let instances = await setUpPolymathNetwork(account_polymath, token_owner); - - [ - I_PolymathRegistry, - I_PolyToken, - I_FeatureRegistry, - I_ModuleRegistry, - I_ModuleRegistryProxy, - I_MRProxied, - I_GeneralTransferManagerFactory, - I_STFactory, - I_SecurityTokenRegistry, - I_SecurityTokenRegistryProxy, - I_STRProxied - ] = instances; - - // STEP 4: Deploy the SingleTradeVolumeRestrictionManagerFactory - [I_SingleTradeVolumeRestrictionManagerFactory] = await deploySingleTradeVolumeRMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); - [P_SingleTradeVolumeRestrictionManagerFactory] = await deploySingleTradeVolumeRMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); - - }); - - describe("Generate the SecurityToken", async () => { - it("Should register the ticker before the generation of the security token", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { - from: token_owner - }); - let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { - from: token_owner - }); - assert.equal(tx.logs[0].args._owner, token_owner); - assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); - }); - - it("Should generate the new security token with the same symbol as registered above", async () => { - await I_PolyToken.approve(I_STRProxied.address, initRegFee, { - from: token_owner - }); - let _blockNo = latestBlock(); - let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { - from: token_owner - }); - - // Verify the successful generation of the security token - assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); - - I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); - - const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ - from: _blockNo - }), 1); - - // Verify that GeneralTransferManager module get added successfully or not - assert.equal(log.args._types[0].toNumber(), 2); - assert.equal( - web3.utils.toAscii(log.args._name) - .replace(/\u0000/g, ''), - "GeneralTransferManager" - ); - }); - - it("Should intialize the auto attached modules", async () => { - let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; - I_GeneralTransferManager = GeneralTransferManager.at(moduleData); - }); - }); - // - describe("Buy tokens using whitelist & manual approvals", async () => { - - it("Should Buy the tokens", async () => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor1, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Jump time - await increaseTime(5000); - - // Mint some tokens - await I_SecurityToken.mint(account_investor1, web3.utils.toWei('100', 'ether'), { - from: token_owner - }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), - web3.utils.toWei('100', 'ether') - ); - }); - - it("Should Buy some more tokens", async () => { - // Add the Investor in to the whitelist - - let tx = await I_GeneralTransferManager.modifyWhitelist( - account_investor2, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, { - from: account_issuer - }); - - assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); - - // Mint some tokens - await I_SecurityToken.mint(account_investor2, web3.utils.toWei('1', 'ether'), { - from: token_owner - }); - - assert.equal( - (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), - web3.utils.toWei('1', 'ether') - ); - }); - // - it("Fails to attach the SingleTradeVolumeRestrictionManager with the security token due to fees not paid", async () => { - let managerArgs = encodeModuleCall(STVRParameters, [true, 90, false]); - - await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); - await catchRevert( - I_SecurityToken.addModule(P_SingleTradeVolumeRestrictionManagerFactory.address, managerArgs, web3.utils.toWei("500", "ether"), 0, { from: token_owner}) - ); - }); - - it("Should successfully attach the Paid SingleTradeVolumeRestrictionManager with the security token", async () => { - let managerArgs = encodeModuleCall(STVRParameters, [false, 90, false]); - await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); - - let tx = await I_SecurityToken.addModule(P_SingleTradeVolumeRestrictionManagerFactory.address, managerArgs, web3.utils.toWei("500", "ether"), 0, { - from: token_owner - }); - - assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "SingleTradeVolumeRestrictionManager did not get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[3].args._name) - .replace(/\u0000/g, ''), - "SingleTradeVolumeRestrictionTM", - "SingleTradeVolumeRestrictionManagerFactory module was not added" - ); - P_SingleTradeVolumeRestrictionManager = SingleTradeVolumeRestrictionManager.at(tx.logs[3].args._module); - }); - - it("Should successfully attach the SingleTradeVolumeRestrictionManager with the security token", async () => { - let managerArgs = encodeModuleCall(STVRParameters, [false, (7 * Math.pow(10, 16)).toString(), false]) - const tx = await I_SecurityToken.addModule(I_SingleTradeVolumeRestrictionManagerFactory.address, managerArgs, 0, 0, { - from: token_owner - }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "TransferManager doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) - .replace(/\u0000/g, ''), - "SingleTradeVolumeRestrictionTM", - "SingleTradeVolumeRestriction module was not added" - ); - I_SingleTradeVolumeRestrictionManager = SingleTradeVolumeRestrictionManager.at(tx.logs[2].args._module); - }); - - it("Should successfully attach the SingleTradeVolumeRestrictionManager (Percentage) with the security token", async () => { - let managerArgs = encodeModuleCall(STVRParameters, [true, 90, false]); - const tx = await I_SecurityToken.addModule(I_SingleTradeVolumeRestrictionManagerFactory.address, managerArgs, 0, 0, { - from: token_owner - }); - assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "PercentageTransferManager doesn't get deployed"); - assert.equal( - web3.utils.toAscii(tx.logs[2].args._name) - .replace(/\u0000/g, ''), - "SingleTradeVolumeRestrictionTM", - "SingleTradeVolumeRestriction module was not added" - ); - I_SingleTradeVolumeRestrictionPercentageManager = SingleTradeVolumeRestrictionManager.at(tx.logs[2].args._module); - }); - - it('should return get permissions', async () => { - let permissions = await I_SingleTradeVolumeRestrictionPercentageManager.getPermissions(); - assert.equal(permissions.length, 1, "Invalid Permissions"); - assert.equal( - web3.utils.toAscii(permissions[0]).replace(/\u0000/g, ''), - "ADMIN", - 'Wrong permissions' - ); - }); - - it("Should allow the primary issuance", async() => { - let snapId = await takeSnapshot(); - await I_SingleTradeVolumeRestrictionManager.setAllowPrimaryIssuance(true, {from: token_owner}); - await catchRevert( - I_SingleTradeVolumeRestrictionManager.setAllowPrimaryIssuance(true, {from: token_owner}) - ) - await revertToSnapshot(snapId); - }) - - it("add exempt wallet -- Not authorised ", async () => { - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.addExemptWallet(accounts[5]) - ); - - await catchRevert( - I_SingleTradeVolumeRestrictionManager.addExemptWallet(zero_address, { from: token_owner }) - ); - - let tx = await I_SingleTradeVolumeRestrictionManager.addExemptWallet(accounts[5], { - from: token_owner - }); - assert.equal(tx.logs[0].args._wallet, accounts[5], "Wrong wallet added as exempt"); - }); - - it("Should remove an exempt wallet", async () => { - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.removeExemptWallet(accounts[5]) - ); - // 0 address are not allowed to add - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.removeExemptWallet(zero_address, { from: token_owner }) - ); - - let tx = await I_SingleTradeVolumeRestrictionManager.removeExemptWallet(accounts[5], { from: token_owner }); - assert.equal(tx.logs[0].args._wallet, accounts[5], "Wrong wallet removed from exempt"); - }); - - it('should set transfer limit for a wallet', async () => { - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.setTransferLimitInTokens(accounts[4], 100) - ); - - // Transfer limits can't be set to 0 - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.setTransferLimitInTokens(accounts[4], 0, { from: token_owner }) - ); - - // Transfer limit cannot be set in percentage - await catchRevert( - I_SingleTradeVolumeRestrictionManager.setTransferLimitInPercentage(accounts[4], 10, { from: token_owner }) - ); - - let tx = await I_SingleTradeVolumeRestrictionManager.setTransferLimitInTokens(accounts[4], 100, { - from: token_owner - }); - assert.equal(tx.logs[0].args._wallet, accounts[4]); - assert.equal(tx.logs[0].args._amount, 100); - - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentage(accounts[4], 0, { from: token_owner }) - ); - // Transfer limit can not be set to more 0 - await catchRevert ( - I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentage(accounts[4], 101 * 10 ** 16, { from: token_owner }) - ); - // Transfer limit in tokens can not be set for a manager that has transfer limit set as percentage - await catchRevert ( - I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInTokens(accounts[4], 1, { from: token_owner }) - ); - - tx = await I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentage(accounts[4], 50, { from: token_owner }); - assert.equal(tx.logs[0].args._wallet, accounts[4], "Wrong wallet added to transfer limits"); - assert.equal(tx.logs[0].args._percentage, 50, "Wrong percentage set"); - }); - - it('should remove transfer limit for wallet', async () => { - // Non Admins cannot set/remove transfer limits - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.removeTransferLimitInTokens(accounts[4]) - ); - - // Non Admins cannot set/remove transfer limits - await catchRevert ( - I_SingleTradeVolumeRestrictionManager.removeTransferLimitInTokens(accounts[0], { from: token_owner }) - ); - - let tx = await I_SingleTradeVolumeRestrictionManager.removeTransferLimitInTokens(accounts[4], { - from: token_owner - }); - assert.equal(tx.logs[0].args._wallet, accounts[4], "Wrong wallet removed"); - }); - - it("Should pause the tranfers at Manager level", async () => { - let tx = await I_SingleTradeVolumeRestrictionManager.pause({ - from: token_owner - }); - }); - - it('Should be able to set a global transfer limit', async () => { - // only owner is allowed - await catchRevert( - I_SingleTradeVolumeRestrictionManager.changeGlobalLimitInTokens(100 * 10 ** 18) - ); - //Cannot change global limit in percentage when set to tokens - await catchRevert( - I_SingleTradeVolumeRestrictionManager.changeGlobalLimitInPercentage(100 * 10 ** 18, { from: token_owner }) - ); - // Global limit cannot be set to 0 - await catchRevert( - I_SingleTradeVolumeRestrictionManager.changeGlobalLimitInTokens(0, { from: token_owner }) - ); - - let tx = await I_SingleTradeVolumeRestrictionManager.changeGlobalLimitInTokens(10, { - from: token_owner - }); - assert.equal(tx.logs[0].args._amount, 10, "Global Limit not set"); - - //Global limit can be set by non-admins - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInTokens(89) - ); - // cannot change global limit in tokens if transfer limit is set to percentage - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInTokens(89, { from: token_owner }) - ); - // Cannot set global limit in tokens to 0 - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInTokens(0, { from: token_owner }) - ); - - tx = await I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInPercentage(40, { from: token_owner }); - assert.equal(tx.logs[0].args._percentage, 40, "Global Limit not set"); - // Global limit cannot be set to more than 100 - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInPercentage(101 * 10 ** 16, { from: token_owner }) - ); - // Global limit in percentage cannot be set when limit is in tokens - await catchRevert( - I_SingleTradeVolumeRestrictionManager.changeGlobalLimitInPercentage(10, { from: token_owner }) - ); - // Global limit in tokens cannot be set when limit is in percentage - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInTokens(10, { from: token_owner }) - ); - }); - - it("Should perform batch updates", async () => { - let wallets = [accounts[0], accounts[1], accounts[2]]; - let tokenLimits = [1, 2, 3]; - let percentageLimits = [5, 6, 7]; - - // Exempt wallet multi cannot be empty wallet - await catchRevert( - P_SingleTradeVolumeRestrictionManager.addExemptWalletMulti([], { from: token_owner }) - ); - - // add exempt wallet multi - let tx = await P_SingleTradeVolumeRestrictionManager.addExemptWalletMulti(wallets, { - from: token_owner - }); - let logs = tx.logs.filter(log => log.event === 'ExemptWalletAdded'); - assert.equal(logs.length, wallets.length, "Batch Exempt wallets not added"); - for (let i = 0; i < logs.length; i++) { - assert.equal(logs[i].args._wallet, wallets[i], "Wallet not added as exempt wallet"); - } - - // Exempt wallet multi cannot be empty wallet - await catchRevert( - P_SingleTradeVolumeRestrictionManager.removeExemptWalletMulti([], { from: token_owner }) - ); - - // remove exempt wallet multi - tx = await P_SingleTradeVolumeRestrictionManager.removeExemptWalletMulti(wallets, { - from: token_owner - }) - logs = tx.logs.filter(log => log.event === 'ExemptWalletRemoved'); - assert.equal(logs.length, wallets.length, "Batch Exempt wallets not removed"); - - for (let i = 0; i < logs.length; i++) { - assert.equal(logs[i].args._wallet, wallets[i], "Wallet not added as exempt wallet"); - } - // wallets cannot be empty - await catchRevert( - P_SingleTradeVolumeRestrictionManager.setTransferLimitInTokensMulti([], tokenLimits, { from: token_owner }) - ); - // wallet array length dont match - await catchRevert( - P_SingleTradeVolumeRestrictionManager.setTransferLimitInTokensMulti([accounts[0]], tokenLimits, { from: token_owner }) - ); - - tx = await P_SingleTradeVolumeRestrictionManager.setTransferLimitInTokensMulti(wallets, tokenLimits, { - from: token_owner - }); - logs = tx.logs.filter(log => log.event == 'TransferLimitInTokensSet'); - assert.equal(wallets.length, logs.length, "Transfer limit not set"); - for (let i = 0; i < wallets.length; i++) { - assert.equal(logs[i].args._wallet, wallets[i], "transfer limit not set for wallet"); - assert.equal(logs[i].args._amount.toNumber(), tokenLimits[i]); - } - // Wallets cannot be empty - await catchRevert( - P_SingleTradeVolumeRestrictionManager.removeTransferLimitInTokensMulti([], { from: token_owner }) - ); - tx = await P_SingleTradeVolumeRestrictionManager.removeTransferLimitInTokensMulti(wallets, { - from: token_owner - }); - logs = tx.logs.filter(log => log.event === 'TransferLimitInTokensRemoved'); - assert.equal(logs.length, wallets.length, "Transfer limit not removed"); - for (let i = 0; i < wallets.length; i++) { - assert.equal(logs[i].args._wallet, wallets[i], "transfer limit not removed for wallet"); - } - // wallets cannot be empty - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentageMulti([], percentageLimits, { from: token_owner }) - ); - // wallets and amounts dont match be empty - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentageMulti(wallets, [], { from: token_owner }) - ); - tx = await I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentageMulti(wallets, percentageLimits, { - from: token_owner - }); - logs = tx.logs.filter(log => log.event == 'TransferLimitInPercentageSet'); - assert.equal(logs.length, wallets.length, "transfer limits not set for wallets"); - - for (let i = 0; i < wallets.length; i++) { - assert.equal(logs[i].args._wallet, wallets[i], "Transfer limit not set for wallet"); - assert.equal(logs[i].args._percentage.toNumber(), percentageLimits[i]); - } - // Wallets cannot be empty - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.removeTransferLimitInPercentageMulti([], { from: token_owner }) - ); - - tx = await I_SingleTradeVolumeRestrictionPercentageManager.removeTransferLimitInPercentageMulti(wallets, { - from: token_owner - }); - logs = tx.logs.filter(log => log.event == 'TransferLimitInPercentageRemoved'); - assert.equal(logs.length, wallets.length, "transfer limits not set for wallets"); - - for (let i = 0; i < wallets.length; i++) { - assert.equal(logs[i].args._wallet, wallets[i], "Transfer limit not set for wallet"); - } - // Wallet should not be removed - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.removeTransferLimitInPercentage(wallets[0], { from: token_owner }) - ); - }) - - it('should be able to transfer tokens SingleTradeVolumeRestriction', async () => { - await I_SingleTradeVolumeRestrictionManager.unpause({ - from: token_owner - }) - await I_SingleTradeVolumeRestrictionPercentageManager.pause({ - from: token_owner - }) - await P_SingleTradeVolumeRestrictionManager.pause({ - from: token_owner - }); - - await I_GeneralTransferManager.modifyWhitelist( - account_investor3, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, { - from: account_issuer - } - ); - - await I_GeneralTransferManager.modifyWhitelist( - account_investor4, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, { - from: account_issuer - } - ); - - await I_GeneralTransferManager.modifyWhitelist( - account_investor5, - latestTime(), - latestTime(), - latestTime() + duration.days(10), - true, { - from: account_issuer - } - ); - - - //setting a max of 5 tokens - await I_SingleTradeVolumeRestrictionManager.changeGlobalLimitInTokens(web3.utils.toWei('5', 'ether'), { - from: token_owner - }) - // Transfer should have not happened - await catchRevert( - I_SecurityToken.transfer(account_investor3, web3.utils.toWei('6', 'ether'), { from: account_investor1 }) - ); - - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('4', 'ether'), { - from: account_investor1 - }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei('4', 'ether')); - - // exempt wallet - await I_SingleTradeVolumeRestrictionManager.addExemptWallet(account_investor1, { - from: token_owner - }); - await I_SecurityToken.transfer(account_investor5, web3.utils.toWei('7', 'ether'), { - from: account_investor1 - }); - assert.equal((await I_SecurityToken.balanceOf(account_investor5)).toNumber(), web3.utils.toWei('7', 'ether')); - - //special limits wallet - await I_SingleTradeVolumeRestrictionManager.setTransferLimitInTokens(account_investor5, web3.utils.toWei('5', 'ether'), { - from: token_owner - }); - - // Transfer should have not happened - await catchRevert( - I_SecurityToken.transfer(account_investor4, web3.utils.toWei('7', 'ether'), { from: account_investor5 }) - ); - - await I_SecurityToken.transfer(account_investor4, web3.utils.toWei('4', 'ether'), { - from: account_investor5 - }) - assert.equal((await I_SecurityToken.balanceOf(account_investor4)).toNumber(), web3.utils.toWei('4', 'ether')) - }) - - it('should be able to transfer tokens (percentage transfer limit)', async () => { - await I_SingleTradeVolumeRestrictionManager.pause({ - from: token_owner - }); - let balance = (await I_SecurityToken.balanceOf(account_investor2)).toNumber(); - await I_SecurityToken.transfer(account_investor1, balance, { - from: account_investor2 - }); - - - balance = (await I_SecurityToken.balanceOf(account_investor3)).toNumber(); - - await I_SecurityToken.transfer(account_investor1, balance, { - from: account_investor3 - }); - - - balance = (await I_SecurityToken.balanceOf(account_investor4)).toNumber(); - await I_SecurityToken.transfer(account_investor1, balance, { - from: account_investor4 - }); - - balance = (await I_SecurityToken.balanceOf(account_investor5)).toNumber(); - await I_SecurityToken.transfer(account_investor1, balance, { - from: account_investor5 - }); - - await I_SingleTradeVolumeRestrictionPercentageManager.unpause({ - from: token_owner - }); - // // - await I_SingleTradeVolumeRestrictionPercentageManager.changeGlobalLimitInPercentage(49 * 10 ** 16, { - from: token_owner - }); - - // Transfer above limit happened - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('90', 'ether'), { from: account_investor1 }) - ); - - await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('20', 'ether'), { - from: account_investor1 - }); - assert.equal((await I_SecurityToken.balanceOf(account_investor2)).toNumber(), web3.utils.toWei('20', 'ether')) - - await I_SingleTradeVolumeRestrictionPercentageManager.setTransferLimitInPercentage(account_investor1, 5 * 10 ** 16, { - from: token_owner - }); - // transfer happened above limit - await catchRevert( - I_SecurityToken.transfer(account_investor2, web3.utils.toWei('35', 'ether'), { from: account_investor1 }) - ); - - await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { - from: account_investor1 - }); - assert.equal((await I_SecurityToken.balanceOf(account_investor3)).toNumber(), web3.utils.toWei('1', 'ether')); - }); - - it('should change transfer limits to tokens', async () => { - // Should not change to percentage again - await catchRevert( - I_SingleTradeVolumeRestrictionPercentageManager.changeTransferLimitToPercentage(1, { from: token_owner }) - ); - - - let tx = await I_SingleTradeVolumeRestrictionPercentageManager.changeTransferLimitToTokens(1, { - from: token_owner - }); - assert.equal(await I_SingleTradeVolumeRestrictionPercentageManager.isTransferLimitInPercentage(), false, "Error Changing"); - assert.equal(tx.logs[0].args._amount.toNumber(), 1, "Transfer limit not changed"); - }) - - it('should change transfer limits to percentage', async () => { - // Should not change to tokens again - await catchRevert( - I_SingleTradeVolumeRestrictionManager.changeTransferLimitToTokens(1, { from: token_owner }) - ); - - let tx = await I_SingleTradeVolumeRestrictionPercentageManager.changeTransferLimitToPercentage(1, { - from: token_owner - }); - assert.ok(await I_SingleTradeVolumeRestrictionPercentageManager.isTransferLimitInPercentage(), "Error Changing"); - assert.equal(tx.logs[0].args._percentage.toNumber(), 1, "Transfer limit not changed"); - }) - - - - }); - - describe("SingleTradeVolumeRestrictionManager Factory test cases", async () => { - - it("Should get the exact details of the factory", async () => { - assert.equal(await I_SingleTradeVolumeRestrictionManagerFactory.getSetupCost.call(), 0); - assert.equal((await I_SingleTradeVolumeRestrictionManagerFactory.getTypes.call())[0], 2); - let name = web3.utils.toUtf8(await I_SingleTradeVolumeRestrictionManagerFactory.getName.call()); - assert.equal(name, "SingleTradeVolumeRestrictionTM", "Wrong Module added"); - let desc = await I_SingleTradeVolumeRestrictionManagerFactory.description.call(); - assert.equal(desc, "Imposes volume restriction on a single trade", "Wrong Module added"); - let title = await I_SingleTradeVolumeRestrictionManagerFactory.title.call(); - assert.equal(title, "Single Trade Volume Restriction Manager", "Wrong Module added"); - let inst = await I_SingleTradeVolumeRestrictionManagerFactory.getInstructions.call(); - assert.equal(inst, "Allows an issuer to impose volume restriction on a single trade. Init function takes two parameters. First parameter is a bool indicating if restriction is in percentage. The second parameter is the value in percentage or amount of tokens", "Wrong Module added"); - let version = await I_SingleTradeVolumeRestrictionManagerFactory.version.call(); - assert.equal(version, "1.0.0", "Version not correct"); - }); - - it("Should get the tags of the factory", async () => { - let tags = await I_SingleTradeVolumeRestrictionManagerFactory.getTags.call(); - assert.equal(web3.utils.toUtf8(tags[0]), "Single Trade"); - assert.equal(web3.utils.toUtf8(tags[1]), "Transfer"); - assert.equal(web3.utils.toUtf8(tags[2]), "Volume"); - }); - - - }); -}); diff --git a/test/y_volume_restriction_tm.js b/test/y_volume_restriction_tm.js new file mode 100644 index 000000000..e456d0c4a --- /dev/null +++ b/test/y_volume_restriction_tm.js @@ -0,0 +1,1878 @@ +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(); + let delegateArray = 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()} + Last Transaction time in UTC: ${(new Date((data[4].toNumber()) * 1000 )).toUTCString()} + `) + } + + async function printRestrictedData(data) { + let investors = data[0]; + for (let i = 0; i < investors.length; i++) { + console.log(` + Token holder: ${data[0][i]} + Start Time: ${data[2][i].toNumber()} + Rolling Period In Days: ${data[3][i].toNumber()} + End Time : ${data[4][i].toNumber()} + Allowed Tokens: ${web3.utils.fromWei(data[1][i].toString())} + Type of Restriction: ${data[5][i].toNumber()} + `) + } + } + + 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 setTime() { + let currentTime = latestTime(); + let currentHour = (new Date(currentTime * 1000)).getUTCHours(); + console.log(`Earlier time ${new Date(latestTime() * 1000).toUTCString()}`); + await increaseTime(duration.hours(24 - currentHour)); + console.log(`Current time ${new Date(latestTime() * 1000).toUTCString()}`); + } + + 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 successfully", async () => { + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("12"), + latestTime() + duration.seconds(10), + 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); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0][0], account_investor1); + }); + + 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(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [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(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [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(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [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(5), latestTime() + duration.seconds(5)], + [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(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [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(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [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(5), latestTime() + duration.seconds(5), latestTime() + duration.seconds(5)], + [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.minutes(1), latestTime() + duration.minutes(1), latestTime() + duration.minutes(1)], + [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); + + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 4); + }); + + 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); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 3); + for (let i = 0; i < data[0].length; i++) { + assert.notEqual(data[0][i], account_investor2); + } + }); + + 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 + } + ) + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 1); + }); + + it("Should add the restriction successfully 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"), + 0, + 3, + latestTime() + duration.days(6), + 0, + { + from: token_owner + } + )} + `); + + let tx = await I_VolumeRestrictionTM.addIndividualRestriction( + account_investor1, + web3.utils.toWei("12"), + 0, + 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); + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 1); + assert.equal(data[0][0], account_investor1); + }); + + it("Should not successfully transact the tokens -- failed because volume is above the limit", async () => { + await increaseTime(duration.seconds(10)); + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei("13"), { from: account_investor1 }) + ); + }); + + it("Should successfully 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(10), + 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 data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 2); + assert.equal(data[0][1], account_investor3); + 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(15); // 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)), + latestTime() + duration.seconds(5), + 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 data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 3); + assert.equal(data[0][2], account_investor3); + assert.equal(data[0][0], account_investor1); + 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 () => { + await increaseTime(6); + 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(10), + 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); + + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + assert.equal(data[0].length, 4); + assert.equal(data[0][2], account_investor3); + assert.equal(data[0][0], account_investor1); + }); + + it("Should transfer the token by the investor 3 with in the (Individual + Individual daily limit)", async () => { + await increaseTime(duration.seconds(15)); + // 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 dataAdd = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(dataAdd); + assert.equal(dataAdd[0].length, 3); + assert.equal(dataAdd[0][0], account_investor1); + assert.equal(dataAdd[0][2], 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'), + latestTime() + duration.seconds(10), + 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 () => { + await increaseTime(duration.seconds(15)); + 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 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)), + latestTime() + duration.seconds(10), + 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 increaseTime(duration.seconds(15)); + 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"), + latestTime() + duration.seconds(10), + 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 Default 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(15); + 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"), + latestTime() + duration.seconds(10), + 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 increaseTime(duration.seconds(15)); + 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 }); + console.log(await I_VolumeRestrictionTM.getExemptAddress.call()); + let beforeBal = await I_SecurityToken.balanceOf.call(account_investor4); + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei("3"), { from: account_investor4 }); + let afterBal = await I_SecurityToken.balanceOf.call(account_investor4); + let diff = beforeBal.minus(afterBal); + assert.equal(web3.utils.fromWei((diff.toNumber()).toString()), 3); + }); + + it("Should add multiple token holders to exemption list and check the getter value", async () => { + let holders = [account_investor1, account_investor3, account_investor2, account_delegate2]; + let change = [true, true, true, true]; + for (let i = 0; i < holders.length; i++) { + await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], { from: token_owner }); + } + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 5); + assert.equal(data[0], account_investor4); + assert.equal(data[1], account_investor1); + assert.equal(data[2], account_investor3); + assert.equal(data[3], account_investor2); + assert.equal(data[4], account_delegate2); + }); + + it("Should unexempt a particular address", async () => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, { from: token_owner }); + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 4); + assert.equal(data[0], account_investor4); + assert.equal(data[1], account_delegate2); + assert.equal(data[2], account_investor3); + assert.equal(data[3], account_investor2); + }); + + it("Should fail to unexempt the same address again", async () => { + await catchRevert( + I_VolumeRestrictionTM.changeExemptWalletList(account_investor1, false, { from: token_owner }) + ); + }); + + it("Should delete the last element of the exemption list", async () => { + await I_VolumeRestrictionTM.changeExemptWalletList(account_investor2, false, { from: token_owner }); + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 3); + assert.equal(data[0], account_investor4); + assert.equal(data[1], account_delegate2); + assert.equal(data[2], account_investor3); + }); + + it("Should delete multiple investor from the exemption list", async () => { + let holders = [account_delegate2, account_investor4, account_investor3]; + let change = [false, false, false]; + for (let i = 0; i < holders.length; i++) { + await I_VolumeRestrictionTM.changeExemptWalletList(holders[i], change[i], { from: token_owner }); + } + let data = await I_VolumeRestrictionTM.getExemptAddress.call(); + assert.equal(data.length, 0); + }); + }); + + describe("Test for modify functions", async () => { + + 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("Test the major issue from the audit ( Possible accounting corruption between individualRestriction​ and ​defaultRestriction)", async() => { + + it("Should add the individual restriction for the delegate 2 address", async() => { + await I_GeneralTransferManager.modifyWhitelist( + account_delegate2, + latestTime(), + latestTime(), + latestTime() + duration.days(30), + true, + { + from: token_owner + } + ); + + await I_SecurityToken.mint(account_delegate2, web3.utils.toWei("50"), { from: token_owner }); + // Use to set the time to start of the day 0:00 to test the edge case properly + await setTime(); + + await I_VolumeRestrictionTM.addIndividualRestriction( + account_delegate2, + web3.utils.toWei("12"), + latestTime() + duration.minutes(1), + 2, + latestTime() + duration.days(5.5), + 0, + { + from: token_owner + } + ); + assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[2].toNumber(), 2); + + let data = await I_VolumeRestrictionTM.getRestrictedData.call(); + await printRestrictedData(data); + + // Add default restriction as well + + await I_VolumeRestrictionTM.removeDefaultRestriction({from: token_owner}); + await I_VolumeRestrictionTM.addDefaultRestriction( + web3.utils.toWei("5"), + latestTime() + duration.minutes(1), + 5, + latestTime() + duration.days(20), + 0, + { + from: token_owner + } + ); + + data = await I_VolumeRestrictionTM.defaultRestriction.call(); + assert.equal(data[0].toNumber(), web3.utils.toWei("5")); + assert.equal(data[2].toNumber(), 5); + let dataRestriction = await I_VolumeRestrictionTM.defaultRestriction.call(); + console.log(` + *** Add Default 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 transact with delegate address 2", async() => { + await increaseTime(duration.minutes(2)); + + let startTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[2].toNumber(); + //sell tokens upto the limit + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("6"), {from: account_delegate2}); + delegateArray.push(6); + + console.log(`Print the default bucket details`); + let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_delegate2); + await print(data, account_delegate2); + assert.equal(data[0].toNumber(), 0); + assert.equal(data[1].toNumber(), 0); + + console.log(`Print the individual bucket details`); + data = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_delegate2); + await print(data, account_delegate2); + + // get the trade amount using the timestamp + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, 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, delegateArray)); + assert.equal(data[2].toNumber(), 0); + assert.equal(amt, 6); + + // Sell more tokens + await increaseTime(duration.days(5.1)); + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei("9"), {from: account_delegate2}); + + delegateArray.push(9); + + console.log(`Print the default bucket details`); + let dataDefault = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_delegate2); + await print(dataDefault, account_delegate2); + assert.equal(dataDefault[0].toNumber(), 0); + assert.equal(dataDefault[1].toNumber(), 0); + console.log(`Print the individual bucket details`); + let dataIndividual = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_delegate2); + await print(dataIndividual, account_delegate2); + + // get the trade amount using the timestamp + let amtTraded = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, dataIndividual[0])) + .dividedBy(new BigNumber(10).pow(18)).toNumber(); + // Verify the storage changes + assert.equal(dataIndividual[0].toNumber(), startTime + duration.days(dataIndividual[2].toNumber())); + assert.equal(dataIndividual[2].toNumber(), 5); + assert.equal(amtTraded, 9); + }); + + it("Should fail to transact -- edge case when user restriction changes and do the transfer on the same day", async() => { + await increaseTime(duration.days(0.6)); + //sell tokens upto the limit + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei("5"), {from: account_delegate2}) + ); + }); + + it("Should transact under the default restriction unaffected from the edge case", async() => { + await increaseTime(duration.days(0.5)); + let individualStartTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.defaultRestriction.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("4"), {from: account_delegate2}); + + console.log(`Print the individual bucket details`); + let dataIndividual = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_delegate2); + await print(dataIndividual, account_delegate2); + console.log(dataIndividual[4].toString()); + + // Verify the storage changes + assert.equal(dataIndividual[0].toNumber(), individualStartTime + duration.days(dataIndividual[2].toNumber())); + assert.equal(dataIndividual[2].toNumber(), 5); + + console.log(`Print the default bucket details`); + let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_delegate2); + await print(data, account_delegate2); + console.log(data[4].toString()); + + // get the trade amount using the timestamp + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, 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[2].toNumber(), 6); + assert.equal(amt, 4); + }); + + it("Should check whether user is able to transfer when amount is less than the restriction limit (when restriction change)", async() => { + + await I_VolumeRestrictionTM.addIndividualRestriction( + account_delegate2, + web3.utils.toWei("7"), + latestTime() + duration.minutes(1), + 1, + latestTime() + duration.days(2), + 0, + { + from: token_owner + } + ); + assert.equal((await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[2].toNumber(), 1); + let individualStartTime = (await I_VolumeRestrictionTM.individualRestriction.call(account_delegate2))[1].toNumber(); + let startTime = (await I_VolumeRestrictionTM.defaultRestriction.call())[1].toNumber(); + let rollingPeriod = (await I_VolumeRestrictionTM.defaultRestriction.call())[2].toNumber(); + + await increaseTime(duration.minutes(2)); + + // sell tokens when user restriction changes from the default restriction to individual restriction + await catchRevert (I_SecurityToken.transfer(account_investor1, web3.utils.toWei("5")), {from: account_delegate2}); + + // allow to transact when the day limit is with in the restriction. default allow to transact maximum 5 tokens within + // a given rolling period. 4 tokens are already sold here user trying to sell 1 more token on the same day + await I_SecurityToken.transfer(account_investor1, web3.utils.toWei("1"), {from: account_delegate2}); + + console.log(`Print the individual bucket details`); + let dataIndividual = await I_VolumeRestrictionTM.getIndividualBucketDetailsToUser.call(account_delegate2); + await print(dataIndividual, account_delegate2); + + // Verify the storage changes + assert.equal(dataIndividual[0].toNumber(), individualStartTime + duration.days(dataIndividual[2].toNumber())); + assert.equal(dataIndividual[2].toNumber(), 0); + // get the trade amount using the timestamp + let amt = (await I_VolumeRestrictionTM.getTotalTradedByUser.call(account_delegate2, dataIndividual[0].toNumber())) + .dividedBy(new BigNumber(10).pow(18)).toNumber(); + assert.equal(amt, 1); + + console.log(`Print the default bucket details`); + let data = await I_VolumeRestrictionTM.getDefaultBucketDetailsToUser.call(account_delegate2); + await print(data, account_delegate2); + + // Verify the storage changes + assert.equal(data[0].toNumber(), startTime + duration.days(data[2].toNumber())); + assert.equal(data[2].toNumber(), 6); + }); + }); + + 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"); + }); + }); + +}); diff --git a/test/z_blacklist_transfer_manager.js b/test/z_blacklist_transfer_manager.js new file mode 100644 index 000000000..0dff595be --- /dev/null +++ b/test/z_blacklist_transfer_manager.js @@ -0,0 +1,986 @@ +import latestTime from './helpers/latestTime'; +import { duration, ensureException, promisifyLogWatch, latestBlock } from './helpers/utils'; +import takeSnapshot, { increaseTime, revertToSnapshot } from './helpers/time'; +import { encodeProxyCall, encodeModuleCall } from './helpers/encodeCall'; +import { setUpPolymathNetwork, deployGPMAndVerifyed, deployBlacklistTMAndVerified } from "./helpers/createInstances"; +import { catchRevert } from "./helpers/exceptions"; + +const GeneralTransferManager = artifacts.require("./GeneralTransferManager"); +const BlacklistTransferManager = artifacts.require("./BlacklistTransferManager"); +const SecurityToken = artifacts.require("./SecurityToken.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('BlacklistTransferManager', accounts => { + + // Accounts Variable declaration + let account_polymath; + let account_issuer; + let token_owner; + let account_investor1; + let account_investor2; + let account_investor3; + let account_investor4; + let account_investor5; + + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let I_SecurityTokenRegistryProxy; + let I_GeneralTransferManagerFactory; + let I_BlacklistTransferManagerFactory; + let I_GeneralPermissionManager; + let I_BlacklistTransferManager; + let P_BlacklistTransferManagerFactory; + let P_BlacklistTransferManager; + let I_GeneralTransferManager; + let I_ExchangeTransferManager; + let I_ModuleRegistry; + let I_ModuleRegistryProxy; + let I_MRProxied; + let I_STRProxied; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_PolymathRegistry; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + // BlacklistTransferManager details + const holderCount = 2; // Maximum number of token holders + const STRProxyParameters = ['address', 'address', 'uint256', 'uint256', 'address', 'address']; + const MRProxyParameters = ['address', 'address']; + let bytesSTO = encodeModuleCall(['uint256'], [holderCount]); + + before(async() => { + // Accounts setup + account_polymath = accounts[0]; + account_issuer = accounts[1]; + + token_owner = account_issuer; + + account_investor1 = accounts[7]; + account_investor2 = accounts[8]; + account_investor3 = accounts[9]; + account_investor4 = accounts[5]; + account_investor5 = accounts[6]; + + 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 2: Deploy the GeneralDelegateManagerFactory + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // STEP 3(a): Deploy the PercentageTransferManager + [I_BlacklistTransferManagerFactory] = await deployBlacklistTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // STEP 4(b): Deploy the PercentageTransferManager + [P_BlacklistTransferManagerFactory] = await deployBlacklistTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500", "ether")); + // ----------- POLYMATH NETWORK Configuration ------------ + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} + + BlacklistTransferManagerFactory: ${I_BlacklistTransferManagerFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner}); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner}); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + + }); + + it("Should successfully attach the BlacklistTransferManager factory with the security token", async () => { + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await catchRevert ( + I_SecurityToken.addModule(P_BlacklistTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { + from: token_owner + }) + ); + }); + + it("Should successfully attach the BlacklistTransferManager factory with the security token", async () => { + let snapId = await takeSnapshot(); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), {from: token_owner}); + const tx = await I_SecurityToken.addModule(P_BlacklistTransferManagerFactory.address, bytesSTO, web3.utils.toWei("500", "ether"), 0, { from: token_owner }); + assert.equal(tx.logs[3].args._types[0].toNumber(), transferManagerKey, "BlacklistTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[3].args._name) + .replace(/\u0000/g, ''), + "BlacklistTransferManager", + "BlacklistTransferManagerFactory module was not added" + ); + P_BlacklistTransferManager = BlacklistTransferManager.at(tx.logs[3].args._module); + await revertToSnapshot(snapId); + }); + + it("Should successfully attach the BlacklistTransferManager with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_BlacklistTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "BlacklistTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "BlacklistTransferManager", + "BlacklistTransferManager module was not added" + ); + I_BlacklistTransferManager = BlacklistTransferManager.at(tx.logs[2].args._module); + }); + + }); + + describe("Buy tokens using on-chain whitelist", async() => { + + it("Should Buy the tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor1, + latestTime(), + latestTime(), + latestTime() + duration.days(50), + true, + { + from: account_issuer + }); + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Jump time + await increaseTime(5000); + + // Mint some tokens + await I_SecurityToken.mint(account_investor1, web3.utils.toWei('5', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor1)).toNumber(), + web3.utils.toWei('5', 'ether') + ); + + }); + + it("Should Buy some more tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor2, + latestTime(), + latestTime(), + latestTime() + duration.days(50), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor2.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor2, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should Buy some more tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor3, + latestTime(), + latestTime(), + latestTime() + duration.days(50), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor3.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor3, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should Buy some more tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor4, + latestTime(), + latestTime(), + latestTime() + duration.days(50), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor4.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor4, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor4)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + it("Should Buy some more tokens", async() => { + // Add the Investor in to the whitelist + + let tx = await I_GeneralTransferManager.modifyWhitelist( + account_investor5, + latestTime(), + latestTime(), + latestTime() + duration.days(50), + true, + { + from: account_issuer + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor5.toLowerCase(), "Failed in adding the investor in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(account_investor5, web3.utils.toWei('2', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(account_investor5)).toNumber(), + web3.utils.toWei('2', 'ether') + ); + }); + + + it("Should add the blacklist", async() => { + //Add the new blacklist + let tx = await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "a_blacklist", 20, { from: token_owner }); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "a_blacklist", "Failed in adding the type in blacklist"); + }); + + it("Should fail in adding the blacklist as blacklist type already exist", async() => { + await catchRevert( + I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "a_blacklist", 20, { + from: token_owner + }) + ); + }); + + it("Should fail in adding the blacklist as the blacklist name is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "", 20, { + from: token_owner + }) + ); + }); + + it("Should fail in adding the blacklist as the start date is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.addBlacklistType(0, latestTime()+3000, "b_blacklist", 20, { + from: token_owner + }) + ); + }); + + it("Should fail in adding the blacklist as the dates are invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.addBlacklistType(latestTime()+4000, latestTime()+3000, "b_blacklist", 20, { + from: token_owner + }) + ); + }); + + it("Should fail in adding the blacklist because only owner can add the blacklist", async() => { + await catchRevert( + I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { + from: account_investor1 + }) + ); + }); + + it("Should fail in adding the blacklist because repeat period is less than the difference of start time and end time", async() => { + await catchRevert( + I_BlacklistTransferManager.addBlacklistType(latestTime() + 1000, latestTime() + duration.days(2), "b_blacklist", 1, { + from: token_owner + }) + ); + }); + + it("Should add the mutiple blacklist", async() => { + //Add the new blacklist + let startTime = [latestTime()+2000,latestTime()+3000]; + let endTime = [latestTime()+5000,latestTime()+8000]; + let name = ["y_blacklist","z_blacklist"]; + let repeatTime = [15,30]; + let tx = await I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { from: token_owner }); + + let event_data = tx.logs; + for (var i = 0; i < event_data.length; i++) { + let blacklistName = event_data[i].args._blacklistName; + assert.equal(web3.utils.hexToUtf8(blacklistName), name[i], "Failed in adding the blacklist"); + } + }); + + it("Should fail in adding the mutiple blacklist because only owner can add it", async() => { + //Add the new blacklist + let startTime = [latestTime()+2000,latestTime()+3000]; + let endTime = [latestTime()+5000,latestTime()+8000]; + let name = ["y_blacklist","z_blacklist"]; + let repeatTime = [15,30]; + await catchRevert( + I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: account_investor1 + }) + ); + }); + + it("Should fail in adding the mutiple blacklist because array lenth are different", async() => { + //Add the new blacklist + let startTime = [latestTime()+2000,latestTime()+3000]; + let endTime = [latestTime()+5000,latestTime()+8000]; + let name = ["y_blacklist","z_blacklist","w_blacklist"]; + let repeatTime = [15,30]; + await catchRevert( + I_BlacklistTransferManager.addBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: token_owner + }) + ); + }); + + it("Should modify the blacklist", async() => { + //Modify the existing blacklist + let tx = await I_BlacklistTransferManager.modifyBlacklistType(latestTime()+2000, latestTime()+3000, "a_blacklist", 20, { from: token_owner }); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "a_blacklist", "Failed in modifying the startdate of blacklist"); + + }); + + it("Should fail in modifying the blacklist as the name is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistType(latestTime()+2000, latestTime()+3000, "", 20, { + from: token_owner + }) + ); + }); + + it("Should fail in modifying the blacklist as the dates are invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistType(latestTime()+4000, latestTime()+3000, "b_blacklist", 20, { + from: token_owner + }) + ); + }); + + it("Should fail in modifying the blacklist as the repeat in days is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistType(latestTime()+2000, latestTime()+3000, "b_blacklist", 0, { + from: token_owner + }) + ); + }); + + + it("Should fail in modifying the blacklist as only owner can modify the blacklist", async() => { + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistType(latestTime()+1000, latestTime()+3000, "a_blacklist", 20, { + from: account_investor1 + }) + ); + }); + + it("Should fail in modifying the blacklist as blacklist type doesnot exist", async() => { + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { + from: token_owner + }) + ); + }); + + it("Should modify the mutiple blacklist", async() => { + //Add the new blacklist + let startTime = [latestTime()+3000,latestTime()+3000]; + let endTime = [latestTime()+5000,latestTime()+7000]; + let name = ["y_blacklist","z_blacklist"]; + let repeatTime = [15,30]; + let tx = await I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { from: token_owner }); + + let event_data = tx.logs; + for (var i = 0; i < event_data.length; i++) { + let blacklistName = event_data[i].args._blacklistName; + assert.equal(web3.utils.hexToUtf8(blacklistName), name[i], "Failed in adding the blacklist"); + } + }); + + it("Should fail in modifying the mutiple blacklist because only owner can add it", async() => { + //Add the new blacklist + let startTime = [latestTime()+3000,latestTime()+3000]; + let endTime = [latestTime()+5000,latestTime()+7000]; + let name = ["y_blacklist","z_blacklist"]; + let repeatTime = [15,30]; + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: account_investor1 + }) + ); + }); + + it("Should fail in modifying the mutiple blacklist because array length are different", async() => { + //Add the new blacklist + let startTime = [latestTime()+3000,latestTime()+3000]; + let endTime = [latestTime()+5000,latestTime()+7000]; + let name = ["y_blacklist","z_blacklist","w_blacklist"]; + let repeatTime = [15,30]; + await catchRevert( + I_BlacklistTransferManager.modifyBlacklistTypeMulti(startTime, endTime, name, repeatTime, { + from: token_owner + }) + ); + }); + + it("Should add investor to the blacklist", async() => { + //Add investor to the existing blacklist + let tx = await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "a_blacklist", { from: token_owner }); + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in adding the investor to the blacklist"); + + }); + + it("Should fail in adding the investor to the blacklist because only owner can add the investor", async() => { + await catchRevert( + I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "a_blacklist", { + from: account_investor1 + }) + ); + }); + + it("Should fail in adding the investor to the blacklist as investor address is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.addInvestorToBlacklist(0x0, "a_blacklist", { + from: token_owner + }) + ); + }); + + + it("Should fail in adding the investor to the non existing blacklist", async() => { + await catchRevert( + I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "b_blacklist", { + from: token_owner + }) + ); + }); + + it("Should get the list of investors associated to blacklist", async() => { + let perm = await I_BlacklistTransferManager.getListOfAddresses.call("a_blacklist"); + assert.equal(perm.length, 1); + }); + + it("Should fail in getting the list of investors from the non existing blacklist", async() => { + await catchRevert( + I_BlacklistTransferManager.getListOfAddresses.call("b_blacklist") + ); + }); + + it("Should investor be able to transfer token because current time is less than the blacklist start time", async() => { + //Trasfer tokens + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('3', 'ether') + ); + }); + + it("Should investor be able to transfer token as it is not in blacklist time period", async() => { + // Jump time + await increaseTime(duration.seconds(4000)); + + //Trasfer tokens + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('4', 'ether') + ); + }); + + it("Should fail in transfer the tokens as the investor in blacklist", async() => { + // Jump time + await increaseTime(duration.days(20) - 1500); + await catchRevert( + I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { + from: account_investor1 + }) + ); + }); + + it("Should investor is able transfer the tokens- because BlacklistTransferManager is paused", async() => { + await I_BlacklistTransferManager.pause({from:token_owner}); + //Trasfer tokens + await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('1', 'ether'), { from: account_investor1 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor2)).toNumber(), + web3.utils.toWei('5', 'ether') + ); + }); + + it("Should investor fail in transfer token as it is in blacklist time period", async() => { + await I_BlacklistTransferManager.unpause({from:token_owner}); + await I_BlacklistTransferManager.addBlacklistType(latestTime()+500, latestTime()+4000, "k_blacklist", 8, { from: token_owner }); + + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "k_blacklist", { from: token_owner }); + // Jump time + await increaseTime(3500); + + //Trasfer tokens + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { + from: account_investor2 + }) + ) + }); + + it("Should investor be able to transfer token as it is not in blacklist time period", async() => { + // Jump time + await increaseTime(1000); + + //Trasfer tokens + await I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { from: account_investor2 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor3)).toNumber(), + web3.utils.toWei('3', 'ether') + ); + }); + + it("Should investor fail in transfer token as it is in blacklist time period", async() => { + + // Jump time + await increaseTime(duration.days(8) - 1000); + //Trasfer tokens + await catchRevert( + I_SecurityToken.transfer(account_investor3, web3.utils.toWei('1', 'ether'), { + from: account_investor2 + }) + ); + }); + + it("Should investor fail in transfer token as it is in blacklist time period", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+5000, latestTime()+8000, "l_blacklist", 5, { from: token_owner }); + + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor3, "l_blacklist", { from: token_owner }); + // Jump time + await increaseTime(5500); + + //Trasfer tokens + await catchRevert( + I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { + from: account_investor3 + }) + ); + }); + + it("Should investor be able to transfer token as it is not in blacklist time period", async() => { + // Jump time + await increaseTime(3000); + + //Trasfer tokens + await I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { from: account_investor3 }); + assert.equal( + (await I_SecurityToken.balanceOf(account_investor4)).toNumber(), + web3.utils.toWei('3', 'ether') + ); + }); + + it("Should investor fail in transfer token as it is in blacklist time period", async() => { + + // Jump time + await increaseTime(duration.days(5) - 3000); + //Trasfer tokens + await catchRevert( + I_SecurityToken.transfer(account_investor4, web3.utils.toWei('1', 'ether'), { + from: account_investor3 + }) + ); + }); + + + it("Should delete the blacklist type", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { from: token_owner }); + let tx = await I_BlacklistTransferManager.deleteBlacklistType("b_blacklist", { from: token_owner }); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "b_blacklist", "Failed in deleting the blacklist"); + + }); + + it("Only owner have the permission to delete thr blacklist type", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "b_blacklist", 20, { from: token_owner }); + await catchRevert( + I_BlacklistTransferManager.deleteBlacklistType("b_blacklist", { + from: account_investor1 + }) + ); + }); + + it("Should fail in deleting the blacklist type as the blacklist has associated addresses", async() => { + await catchRevert( + I_BlacklistTransferManager.deleteBlacklistType("a_blacklist", { + from: token_owner + }) + ); + }); + + it("Should fail in deleting the blacklist type as the blacklist doesnot exist", async() => { + await catchRevert( + I_BlacklistTransferManager.deleteBlacklistType("c_blacklist", { + from: token_owner + }) + ); + }); + + it("Should delete the mutiple blacklist type", async() => { + let name = ["y_blacklist","z_blacklist"]; + let tx = await I_BlacklistTransferManager.deleteBlacklistTypeMulti(name, { from: token_owner }); + + let event_data = tx.logs; + for (var i = 0; i < event_data.length; i++) { + let blacklistName = event_data[i].args._blacklistName; + assert.equal(web3.utils.hexToUtf8(blacklistName), name[i], "Failed in deleting the blacklist"); + } + + }); + + it("Should fail in deleting multiple blacklist type because only owner can do it.", async() => { + let name = ["b_blacklist","a_blacklist"]; + await catchRevert( + I_BlacklistTransferManager.deleteBlacklistTypeMulti(name, { + from: account_investor1 + }) + ); + }); + + it("Should delete the investor from all the associated blacklist", async() => { + let data = await I_BlacklistTransferManager.getBlacklistNamesToUser.call(account_investor1); + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "g_blacklist", 20, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "g_blacklist", { from: token_owner }); + let tx = await I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor1, { from: token_owner }); + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in deleting the investor from the blacklist"); + }); + + it("Only owner has the permission to delete the investor from all the blacklist type", async() => { + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "g_blacklist", { from: token_owner }); + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor1, { + from: account_investor2 + }) + ) + }); + + it("Should fail in deleting the investor from all the associated blacklist as th address is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(0x0, { + from: token_owner + }) + ); + }); + + it("Should fail in deleting the investor because investor is not associated to any blacklist", async() => { + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromAllBlacklist(account_investor5, { + from: token_owner + }) + ); + }); + + it("Should delete the mutiple investor from all the associated blacklist", async() => { + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + let investor = [account_investor5,account_investor2]; + let tx = await I_BlacklistTransferManager.deleteInvestorFromAllBlacklistMulti(investor, { from: token_owner }); + let event_data = tx.logs; + assert.equal(event_data[0].args._investor, investor[0], "Failed in deleting the blacklist"); + assert.equal(event_data[1].args._investor, investor[1], "Failed in deleting the blacklist"); + assert.equal(event_data[2].args._investor, investor[1], "Failed in deleting the blacklist"); + }); + + it("Should fail in deleting the mutiple investor from all the associated blacklist because only owner can do it.", async() => { + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "g_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + let investor = [account_investor5,account_investor2]; + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromAllBlacklistMulti(investor, { + from: account_investor1 + }) + ); + }); + + it("Should delete the mutiple investor from particular associated blacklists", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "s_blacklist", 20, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "s_blacklist", { from: token_owner }); + // await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + let investor = [account_investor5,account_investor2]; + let blacklistName = ["s_blacklist","g_blacklist"]; + let tx = await I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { from: token_owner }); + let event_data = tx.logs; + for (var i = 0; i < event_data.length; i++) { + let investorName = event_data[i].args._investor; + assert.equal(investorName.toLowerCase(), investor[i].toLowerCase(), "Failed in deleting the blacklist"); + } + }); + + it("Should fail in deleting the mutiple investor from particular associated blacklist because only owner can do it.", async() => { + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "s_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor2, "g_blacklist", { from: token_owner }); + let investor = [account_investor5,account_investor2]; + let blacklistName = ["s_blacklist","g_blacklist"]; + await catchRevert( + I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { + from: account_investor1 + }) + ); + }); + + it("Should fail in deleting the mutiple investor from particular associated blacklist because array length is incorrect.", async() => { + let investor = [account_investor5]; + let blacklistName = ["s_blacklist","g_blacklist"]; + await catchRevert( + I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { + from: token_owner + }) + ); + }); + + it("Should delete the investor from the blacklist type", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "f_blacklist", 20, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor5, "f_blacklist", { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(latestTime()+500, latestTime()+8000, "q_blacklist", 10, { from: token_owner }); + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "q_blacklist", { from: token_owner }); + let tx = await I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + assert.equal(tx.logs[0].args._investor.toLowerCase(), account_investor1.toLowerCase(), "Failed in deleting the investor from the blacklist"); + + }); + + it("Only owner can delete the investor from the blacklist type", async() => { + await I_BlacklistTransferManager.addInvestorToBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { + from: account_investor2 + }) + ); + }); + + it("Should fail in deleting the investor because the investor address is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromBlacklist(0x0, "f_blacklist", { + from: token_owner + }) + ); + }); + + it("Should fail in deleting the investor because the investor is not associated to blacklist", async() => { + await I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { from: token_owner }); + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "f_blacklist", { + from: token_owner + }) + ); + }); + + it("Should fail in deleting the investor because the blacklist name is invalid", async() => { + await catchRevert( + I_BlacklistTransferManager.deleteInvestorFromBlacklist(account_investor1, "", { + from: token_owner + }) + ); + }); + + it("Should add investor and new blacklist type", async() => { + let tx = await I_BlacklistTransferManager.addInvestorToNewBlacklist(latestTime()+1000, latestTime()+3000, "c_blacklist", 20, account_investor3, { from: token_owner }); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._blacklistName), "c_blacklist", "Failed in adding the blacklist"); + assert.equal(tx.logs[1].args._investor, account_investor3, "Failed in adding the investor to blacklist"); + + }); + + it("Should fail in adding the investor and new blacklist type", async() => { + await catchRevert( + I_BlacklistTransferManager.addInvestorToNewBlacklist(latestTime()+1000, latestTime()+3000, "c_blacklist", 20, account_investor3, { + from: account_investor2 + }) + ); + }); + + it("Should add mutiple investor to blacklist", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "d_blacklist", 20, { from: token_owner }); + let investor = [account_investor4,account_investor5]; + let tx = await I_BlacklistTransferManager.addInvestorToBlacklistMulti([account_investor4,account_investor5], "d_blacklist", { from: token_owner }); + + let event_data = tx.logs; + for (var i = 0; i < event_data.length; i++) { + let user = event_data[i].args._investor; + assert.equal(user, investor[i], "Failed in adding the investor to blacklist"); + } + + }); + + it("Should fail in adding the mutiple investor to the blacklist", async() => { + await catchRevert( + I_BlacklistTransferManager.addInvestorToBlacklistMulti([account_investor4,account_investor5], "b_blacklist", { + from: account_investor1 + }) + ); + }); + + it("Should add mutiple investor to the mutiple blacklist", async() => { + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "m_blacklist", 20, { from: token_owner }); + await I_BlacklistTransferManager.addBlacklistType(latestTime()+1000, latestTime()+3000, "n_blacklist", 20, { from: token_owner }); + let investor = [account_investor4,account_investor5]; + let blacklistName =["m_blacklist","n_blacklist"]; + let tx = await I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { from: token_owner }); + + let event_data = tx.logs; + for (var i = 0; i < event_data.length; i++) { + let user = event_data[i].args._investor; + let blacklist = event_data[i].args._blacklistName; + assert.equal(user, investor[i], "Failed in adding the investor to blacklist"); + assert.equal(web3.utils.hexToUtf8(blacklist), blacklistName[i], "Failed in adding the investor to blacklist"); + } + + }); + + it("Should fail in adding the mutiple investor to the mutiple blacklist because only owner can do it.", async() => { + let investor = [account_investor4,account_investor5]; + let blacklistName = ["m_blacklist","n_blacklist"]; + await I_BlacklistTransferManager.deleteMultiInvestorsFromBlacklistMulti(investor,blacklistName, { from: token_owner }); + await catchRevert( + I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { + from: account_investor1 + }) + ); + }); + + it("Should fail in adding mutiple investor to the mutiple blacklist because array length is not same", async() => { + let investor = [account_investor4,account_investor5]; + let blacklistName =["m_blacklist"]; + await catchRevert( + I_BlacklistTransferManager.addMultiInvestorToBlacklistMulti(investor, blacklistName, { + from: token_owner + }) + ); + }); + + it("Should get the init function", async() => { + let byte = await I_BlacklistTransferManager.getInitFunction.call(); + assert.equal(web3.utils.toAscii(byte).replace(/\u0000/g, ''), 0); + }); + + it("Should get the permission", async() => { + let perm = await I_BlacklistTransferManager.getPermissions.call(); + assert.equal(perm.length, 1); + }); + }); + + describe("Test cases for the factory", async() => { + it("Should get the exact details of the factory", async() => { + assert.equal(await I_BlacklistTransferManagerFactory.setupCost.call(),0); + assert.equal((await I_BlacklistTransferManagerFactory.getTypes.call())[0],2); + assert.equal(web3.utils.toAscii(await I_BlacklistTransferManagerFactory.getName.call()) + .replace(/\u0000/g, ''), + "BlacklistTransferManager", + "Wrong Module added"); + assert.equal(await I_BlacklistTransferManagerFactory.description.call(), + "Automate blacklist to restrict selling", + "Wrong Module added"); + assert.equal(await I_BlacklistTransferManagerFactory.title.call(), + "Blacklist Transfer Manager", + "Wrong Module added"); + assert.equal(await I_BlacklistTransferManagerFactory.getInstructions.call(), + "Allows an issuer to blacklist the addresses.", + "Wrong Module added"); + assert.equal(await I_BlacklistTransferManagerFactory.version.call(), + "2.1.0", + "Wrong Module added"); + + }); + + it("Should get the tags of the factory", async() => { + let tags = await I_BlacklistTransferManagerFactory.getTags.call(); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ''),"Blacklist"); + }); + }); + +}); diff --git a/test/z_fuzz_test_adding_removing_modules_ST.js b/test/z_fuzz_test_adding_removing_modules_ST.js new file mode 100644 index 000000000..5b6c8b57d --- /dev/null +++ b/test/z_fuzz_test_adding_removing_modules_ST.js @@ -0,0 +1,310 @@ +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, + deployGPMAndVerifyed, + deployCountTMAndVerifyed, + deployLockupVolumeRTMAndVerified, + deployPercentageTMAndVerified, + deployManualApprovalTMAndVerifyed +} from "./helpers/createInstances"; +import { encodeModuleCall } from "./helpers/encodeCall"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); + +// modules for test +const CountTransferManager = artifacts.require("./CountTransferManager"); +const ManualApprovalTransferManager = artifacts.require('./ManualApprovalTransferManager'); +const VolumeRestrictionTransferManager = artifacts.require('./LockUpTransferManager'); +const PercentageTransferManager = artifacts.require('./PercentageTransferManager'); + + + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('GeneralPermissionManager', 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; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let P_GeneralPermissionManagerFactory; + let I_SecurityTokenRegistryProxy; + let P_GeneralPermissionManager; + let I_GeneralTransferManagerFactory; + let I_GeneralPermissionManager; + 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; + + + //Define all modules for test + let I_CountTransferManagerFactory; + let I_CountTransferManager; + + let I_ManualApprovalTransferManagerFactory; + let I_ManualApprovalTransferManager; + + let I_VolumeRestrictionTransferManagerFactory; + let I_VolumeRestrictionTransferManager; + + let I_PercentageTransferManagerFactory; + let I_PercentageTransferManager; + + // 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"; + const STVRParameters = ["bool", "uint256", "bool"]; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + let _details = "details holding for test"; + let testRepeat = 20; + + // define factories and modules for fuzz test + var factoriesAndModules = [ + { factory: 'I_CountTransferManagerFactory', module: 'CountTransferManager'}, + { factory: 'I_ManualApprovalTransferManagerFactory', module: 'ManualApprovalTransferManager'}, + { factory: 'I_VolumeRestrictionTransferManagerFactory', module: 'VolumeRestrictionTransferManager'}, + { factory: 'I_PercentageTransferManagerFactory', module: 'PercentageTransferManager'}, + ]; + + let totalModules = factoriesAndModules.length; + let bytesSTO; + + + 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[5]; + account_investor4 = accounts[6]; + account_delegate = accounts[7]; + // account_delegate2 = accounts[6]; + + + // 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 GeneralDelegateManagerFactory + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 6: Deploy the GeneralDelegateManagerFactory + [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // Deploy Modules + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // 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} + GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async () => { + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + }); + + it("Should successfully attach the General permission manager factory with the security token -- failed because Token is not paid", async () => { + let errorThrown = false; + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await catchRevert( + I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + ); + }); + + it("Should successfully attach the General permission manager factory with the security token", async () => { + let snapId = await takeSnapshot(); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + const tx = await I_SecurityToken.addModule( + P_GeneralPermissionManagerFactory.address, + "0x", + web3.utils.toWei("500", "ether"), + 0, + { from: token_owner } + ); + assert.equal(tx.logs[3].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[3].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" + ); + P_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[3].args._module); + await revertToSnapshot(snapId); + }); + + it("Should successfully attach the General permission manager factory with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" + ); + I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + }); + }); + + + + describe("adding and removing different modules", async () => { + + it("should pass test for randomly adding and removing modules ", async () => { + + console.log("1"); + // fuzz test loop over total times of testRepeat + for (var i = 0; i < testRepeat; i++) { + + console.log("1.2"); + + // choose a random module with in the totalMoudules available + let random = factoriesAndModules[Math.floor(Math.random() * Math.floor(totalModules))]; + let randomFactory = eval(random.factory); + let randomModule = eval(random.module); + console.log("choosen factory "+ random.factory); + console.log("choosen module "+ random.module); + + //calculate the data needed for different modules + if (random.module == 'CountTransferManager' || random.module == 'ManualApprovalTransferManager' || random.module == 'VolumeRestrictionTransferManager' ){ + const holderCount = 2; // Maximum number of token holders + bytesSTO = encodeModuleCall(["uint256"], [holderCount]); + } else if (random.module == 'PercentageTransferManager'){ + console.log("PTM 01"); + const holderPercentage = 70 * 10**16; + bytesSTO = web3.eth.abi.encodeFunctionCall({ + name: 'configure', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_maxHolderPercentage' + },{ + type: 'bool', + name: '_allowPrimaryIssuance' + } + ] + }, [holderPercentage, false]); + console.log("encoded."); + } else { + console.log("no data defined for choosen module "+random.module); + } + + // attach it to the ST + let tx = await I_SecurityToken.addModule(randomFactory.address, bytesSTO, 0, 0, { from: token_owner }); + console.log("1.3"); + let randomModuleInstance = randomModule.at(tx.logs[2].args._module); + console.log("successfully attached module " + randomModuleInstance.address); + + // remove it from the ST + tx = await I_SecurityToken.archiveModule(randomModuleInstance.address, { from: token_owner }); + console.log("1.4"); + tx = await I_SecurityToken.removeModule(randomModuleInstance.address, { from: token_owner }); + console.log("successfully removed module " + randomModuleInstance.address); + + } + }) + }); + +}); 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/test/z_general_permission_manager_fuzzer.js b/test/z_general_permission_manager_fuzzer.js new file mode 100644 index 000000000..d80b36094 --- /dev/null +++ b/test/z_general_permission_manager_fuzzer.js @@ -0,0 +1,600 @@ +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, + deployGPMAndVerifyed, + deployCountTMAndVerifyed, + deployLockupVolumeRTMAndVerified, + deployPercentageTMAndVerified, + deployManualApprovalTMAndVerifyed +} from "./helpers/createInstances"; +import { encodeModuleCall } from "./helpers/encodeCall"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require('./GeneralPermissionManager'); +const CountTransferManager = artifacts.require("./CountTransferManager"); +const PercentageTransferManager = artifacts.require('./PercentageTransferManager'); +const ManualApprovalTransferManager = artifacts.require('./ManualApprovalTransferManager'); + +const Web3 = require('web3'); +const BigNumber = require('bignumber.js'); +const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) // Hardcoded development port + +contract('GeneralPermissionManager', 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; + // investor Details + let fromTime = latestTime(); + let toTime = latestTime(); + let expiryTime = toTime + duration.days(15); + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_GeneralPermissionManagerFactory; + let P_GeneralPermissionManagerFactory; + let I_SecurityTokenRegistryProxy; + let P_GeneralPermissionManager; + let I_GeneralTransferManagerFactory; + let I_VolumeRestrictionTransferManagerFactory; + let I_PercentageTransferManagerFactory; + let I_PercentageTransferManager; + let I_VolumeRestrictionTransferManager; + let I_GeneralPermissionManager; + 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; + let I_CountTransferManagerFactory; + let I_CountTransferManager; + let I_ManualApprovalTransferManagerFactory; + let I_ManualApprovalTransferManager; + + // 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"; + const STVRParameters = ["bool", "uint256", "bool"]; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + // CountTransferManager details + const holderCount = 2; // Maximum number of token holders + let bytesSTO = encodeModuleCall(["uint256"], [holderCount]); + + let _details = "details holding for test"; + let testRepeat = 20; + + // permission manager fuzz test + let perms = ['ADMIN','WHITELIST', 'FLAGS', 'TRANSFER_APPROVAL']; + let totalPerms = perms.length; + + 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[5]; + account_investor4 = accounts[6]; + account_delegate = accounts[7]; + // account_delegate2 = accounts[6]; + + + // 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 GeneralDelegateManagerFactory + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + // STEP 6: Deploy the GeneralDelegateManagerFactory + [P_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, web3.utils.toWei("500")); + + // Deploy Modules + [I_CountTransferManagerFactory] = await deployCountTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + [I_VolumeRestrictionTransferManagerFactory] = await deployLockupVolumeRTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + [I_PercentageTransferManagerFactory] = await deployPercentageTMAndVerified(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + [I_ManualApprovalTransferManagerFactory] = await deployManualApprovalTMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // 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} + GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async () => { + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from: token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({ from: _blockNo }), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal(web3.utils.toAscii(log.args._name).replace(/\u0000/g, ""), "GeneralTransferManager"); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + }); + + it("Should successfully attach the General permission manager factory with the security token -- failed because Token is not paid", async () => { + let errorThrown = false; + await I_PolyToken.getTokens(web3.utils.toWei("500", "ether"), token_owner); + await catchRevert( + I_SecurityToken.addModule(P_GeneralPermissionManagerFactory.address, "0x", web3.utils.toWei("500", "ether"), 0, { from: token_owner }) + ); + }); + + it("Should successfully attach the General permission manager factory with the security token", async () => { + let snapId = await takeSnapshot(); + await I_PolyToken.transfer(I_SecurityToken.address, web3.utils.toWei("500", "ether"), { from: token_owner }); + const tx = await I_SecurityToken.addModule( + P_GeneralPermissionManagerFactory.address, + "0x", + web3.utils.toWei("500", "ether"), + 0, + { from: token_owner } + ); + assert.equal(tx.logs[3].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[3].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" + ); + P_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[3].args._module); + await revertToSnapshot(snapId); + }); + + it("Should successfully attach the General permission manager factory with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" + ); + I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + }); + }); + + describe("fuzz test for general transfer manager", async () => { + + it("should pass fuzz test for changeIssuanceAddress(), changeSigningAddress() ", async () => { + + console.log("1"); + // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts + for (var i = 2; i < testRepeat; i++) { + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms + + // add account as a Delegate if it is not + if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + } + + // target permission should alaways be false for each test before assigning + if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, 'FLAGS') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, 'FLAGS', false, { from: token_owner }); + } else if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, 'WHITELIST') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, 'WHITELIST', false, { from: token_owner }); + } + + // assign a random perm + let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; + let fromTime = latestTime(); + let toTime = latestTime() + duration.days(20); + let expiryTime = toTime + duration.days(10); + + await I_GeneralPermissionManager.changePermission(accounts[j], I_GeneralTransferManager.address, randomPerms, true, { from: token_owner }); + + let currentAllowAllTransferStats = await I_GeneralTransferManager.allowAllTransfers(); + let currentAllowAllWhitelistTransfersStats = await I_GeneralTransferManager.allowAllWhitelistTransfers(); + let currentAllowAllWhitelistIssuancesStats = await I_GeneralTransferManager.allowAllWhitelistIssuances(); + let currentAllowAllBurnTransfersStats = await I_GeneralTransferManager.allowAllBurnTransfers(); + console.log("2"); + // let userPerm = await I_GeneralPermissionManager.checkPermission(accounts[j], I_GeneralTransferManager.address, 'FLAGS'); + if (randomPerms === 'FLAGS') { + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " about to start") + await I_GeneralTransferManager.changeIssuanceAddress( accounts[j], { from: accounts[j] }); + assert.equal(await I_GeneralTransferManager.issuanceAddress(), accounts[j]); + + await I_GeneralTransferManager.changeSigningAddress( accounts[j], { from: accounts[j] }); + assert.equal(await I_GeneralTransferManager.signingAddress(), accounts[j]); + + await I_GeneralTransferManager.changeAllowAllTransfers(!currentAllowAllTransferStats, { from: accounts[j] }); + assert.equal(await I_GeneralTransferManager.allowAllTransfers(), !currentAllowAllTransferStats); + + await I_GeneralTransferManager.changeAllowAllWhitelistTransfers(!currentAllowAllWhitelistTransfersStats, { from: accounts[j] }); + assert.equal(await I_GeneralTransferManager.allowAllWhitelistTransfers(), !currentAllowAllWhitelistTransfersStats); + + await I_GeneralTransferManager.changeAllowAllWhitelistIssuances(!currentAllowAllWhitelistIssuancesStats, { from: accounts[j] }); + assert.equal(await I_GeneralTransferManager.allowAllWhitelistIssuances(), !currentAllowAllWhitelistIssuancesStats); + + await I_GeneralTransferManager.changeAllowAllBurnTransfers(!currentAllowAllBurnTransfersStats, { from: accounts[j] }); + assert.equal(await I_GeneralTransferManager.allowAllBurnTransfers(), !currentAllowAllBurnTransfersStats); + + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " for functions with require perm FLAGS passed") + } else { + await catchRevert(I_GeneralTransferManager.changeIssuanceAddress( accounts[j], { from: accounts[j] })); + await catchRevert(I_GeneralTransferManager.changeSigningAddress( accounts[j], { from: accounts[j] })); + await catchRevert(I_GeneralTransferManager.changeAllowAllTransfers( !currentAllowAllTransferStats, { from: accounts[j] })); + await catchRevert(I_GeneralTransferManager.changeAllowAllWhitelistTransfers( !currentAllowAllWhitelistTransfersStats, { from: accounts[j] })); + await catchRevert(I_GeneralTransferManager.changeAllowAllWhitelistIssuances( !currentAllowAllWhitelistIssuancesStats, { from: accounts[j] })); + await catchRevert(I_GeneralTransferManager.changeAllowAllBurnTransfers( !currentAllowAllBurnTransfersStats, { from: accounts[j] })); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " for functions require perm FLAGS failed as expected"); + } + + console.log("3"); + if (randomPerms === 'WHITELIST') { + let tx = await I_GeneralTransferManager.modifyWhitelist(accounts[j], fromTime, toTime, expiryTime, 1, { from: accounts[j] }); + assert.equal(tx.logs[0].args._investor, accounts[j]); + console.log("3.1"); + let tx2 = await I_GeneralTransferManager.modifyWhitelistMulti([accounts[3], accounts[4]], [fromTime, fromTime], [toTime, toTime], [expiryTime, expiryTime], [1, 1], { from: accounts[j] }); + console.log(tx2.logs[1].args); + assert.equal(tx2.logs[1].args._investor, accounts[4]); + console.log("3.2"); + } else { + console.log("3.3"); + await catchRevert(I_GeneralTransferManager.modifyWhitelist(accounts[j], fromTime, toTime, expiryTime, 1, { from: accounts[j] })); + console.log("3.4"); + await catchRevert(I_GeneralTransferManager.modifyWhitelistMulti([accounts[3], accounts[4]], [fromTime, fromTime], [toTime, toTime], [expiryTime, expiryTime], [1, 1], { from: accounts[j] })); + console.log("3.5"); + } + } + console.log("4"); + await I_GeneralTransferManager.changeIssuanceAddress("0x0000000000000000000000000000000000000000", { from: token_owner }); + }) + }); + + describe("fuzz test for count transfer manager", async () => { + + it("Should successfully attach the CountTransferManager with the security token", async () => { + const tx = await I_SecurityToken.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_CountTransferManager = CountTransferManager.at(tx.logs[2].args._module); + }); + + it("should pass fuzz test for changeHolderCount()", async () => { + // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts + for (var i = 2; i < testRepeat; i++) { + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms + + // add account as a Delegate if it is not + if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + } + + // target permission should alaways be false for each test before assigning + if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_CountTransferManager.address, 'ADMIN') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_CountTransferManager.address, 'ADMIN', false, { from: token_owner }); + } + + // assign a random perm + let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; + await I_GeneralPermissionManager.changePermission(accounts[j], I_CountTransferManager.address, randomPerms, true, { from: token_owner }); + if (randomPerms === 'ADMIN') { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); + await I_CountTransferManager.changeHolderCount(i + 1, { from: accounts[j] }); + assert.equal((await I_CountTransferManager.maxHolderCount()).toNumber(), i + 1); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " passed"); + } else { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); + await catchRevert(I_CountTransferManager.changeHolderCount(i+1, { from: accounts[j] })); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); + } + } + }); + }); + + + describe("fuzz test for percentage transfer manager", async () => { + + // PercentageTransferManager details + const holderPercentage = 70 * 10**16; // Maximum number of token holders + + let bytesSTO = web3.eth.abi.encodeFunctionCall({ + name: 'configure', + type: 'function', + inputs: [{ + type: 'uint256', + name: '_maxHolderPercentage' + },{ + type: 'bool', + name: '_allowPrimaryIssuance' + } + ] + }, [holderPercentage, false]); + + it("Should successfully attach the percentage transfer manager with the security token", async () => { + console.log("1"); + const tx = await I_SecurityToken.addModule(I_PercentageTransferManagerFactory.address, bytesSTO, 0, 0, { from: token_owner }); + I_PercentageTransferManager = PercentageTransferManager.at(tx.logs[2].args._module); + }); + + it("should pass fuzz test for modifyWhitelist with perm WHITELIST", async () => { + // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts + for (var i = 2; i < testRepeat; i++) { + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms + + // add account as a Delegate if it is not + if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + } + + // target permission should alaways be false for each test before assigning + if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST', false, { from: token_owner }); + } + + // assign a random perm + let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, randomPerms, true, { from: token_owner }); + + //try add multi lock ups + if (randomPerms === 'WHITELIST') { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); + await I_PercentageTransferManager.modifyWhitelist(account_investor3, 1, { from: accounts[j] }); + console.log("Test number " + i + " with account " + j + " and perm WHITELIST passed as expected"); + } else { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); + await catchRevert(I_PercentageTransferManager.modifyWhitelist(account_investor3, 1, { from: accounts[j] })); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); + } + } + }); + + it("should pass fuzz test for modifyWhitelistMulti with perm WHITELIST", async () => { + // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts + for (var i = 2; i < testRepeat; i++) { + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms + + // add account as a Delegate if it is not + if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + } + + // target permission should alaways be false for each test before assigning + if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, 'WHITELIST', false, { from: token_owner }); + } + + // assign a random perm + let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, randomPerms, true, { from: token_owner }); + + if (randomPerms === 'WHITELIST') { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); + await I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [0, 1], { from: accounts[j] }); + console.log("Test number " + i + " with account " + j + " and perm WHITELIST passed as expected"); + + } else { + // console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); + await catchRevert( I_PercentageTransferManager.modifyWhitelistMulti([account_investor3, account_investor4], [0, 1], { from: accounts[j] })); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); + } + } + }); + + it("should pass fuzz test for setAllowPrimaryIssuance with perm ADMIN", async () => { + + // let snapId = await takeSnapshot(); + // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts + for (var i = 2; i < testRepeat; i++) { + + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms + + // add account as a Delegate if it is not + if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + } + + // target permission should alaways be false for each test before assigning + if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_PercentageTransferManager.address, 'ADMIN') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, 'ADMIN', false, { from: token_owner }); + } + + // assign a random perm + let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; + await I_GeneralPermissionManager.changePermission(accounts[j], I_PercentageTransferManager.address, randomPerms, true, { from: token_owner }); + + let primaryIssuanceStat = await I_PercentageTransferManager.allowPrimaryIssuance({ from: token_owner }); + + if (randomPerms === 'ADMIN') { + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should pass"); + await I_PercentageTransferManager.setAllowPrimaryIssuance(!primaryIssuanceStat, { from: accounts[j] }); + console.log("Test number " + i + " with account " + j + " and perm ADMIN passed as expected"); + + } else { + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); + await catchRevert( I_PercentageTransferManager.setAllowPrimaryIssuance(!primaryIssuanceStat, { from: accounts[j] })); + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); + } + // await revertToSnapshot(snapId); + } + }); + }); + + + describe("fuzz test for manual approval transfer manager", async () => { + + it("Should successfully attach the ManualApprovalTransferManager with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_ManualApprovalTransferManagerFactory.address, "", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), transferManagerKey, "ManualApprovalTransferManager doesn't get deployed"); + assert.equal( + web3.utils.toUtf8(tx.logs[2].args._name), + "ManualApprovalTransferManager", + "ManualApprovalTransferManager module was not added" + ); + I_ManualApprovalTransferManager = ManualApprovalTransferManager.at(tx.logs[2].args._module); + }); + + it("should pass fuzz test for addManualApproval & revokeManualApproval with perm TRANSFER_APPROVAL", async () => { + + let tx; + // fuzz test loop over total times of testRepeat, inside each loop, we use a variable j to randomly choose an account out of the 10 default accounts + for (var i = 2; i < testRepeat; i++) { + + let snapId = await takeSnapshot(); + + + var j = Math.floor(Math.random() * 10); + if (j === 1 || j === 0) { j = 2 }; // exclude account 1 & 0 because they might come with default perms + + // add account as a Delegate if it is not + if (await I_GeneralPermissionManager.checkDelegate(accounts[j]) !== true) { + await I_GeneralPermissionManager.addDelegate(accounts[j], _details, { from: token_owner }); + } + + // target permission should alaways be false for each test before assigning + if (await I_GeneralPermissionManager.checkPermission(accounts[j], I_ManualApprovalTransferManager.address, 'TRANSFER_APPROVAL') === true) { + await I_GeneralPermissionManager.changePermission(accounts[j], I_ManualApprovalTransferManager.address, 'TRANSFER_APPROVAL', false, { from: token_owner }); + } + + // assign a random perm + let randomPerms = perms[Math.floor(Math.random() * Math.floor(totalPerms))]; + await I_GeneralPermissionManager.changePermission(accounts[j], I_ManualApprovalTransferManager.address, randomPerms, true, { from: token_owner }); + + if (randomPerms === "TRANSFER_APPROVAL" ) { + console.log("Test number " + i + " with account " + j + " and perm TRANSFER_APPROVAL " + " should pass"); + await I_ManualApprovalTransferManager.addManualApproval( + account_investor1, + account_investor4, + web3.utils.toWei("2", "ether"), + latestTime() + duration.days(1), + "ABC", + { from: accounts[j] } + ); + + console.log("2"); + tx = await I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, { + from: accounts[j] + }); + assert.equal(tx.logs[0].args._from, account_investor1); + assert.equal(tx.logs[0].args._to, account_investor4); + assert.equal(tx.logs[0].args._addedBy, accounts[j]); + + console.log("3"); + console.log("Test number " + i + " with account " + j + " and perm TRANSFER_APPROVAL passed as expected"); + } else { + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " should failed"); + await catchRevert( + I_ManualApprovalTransferManager.addManualApproval( + account_investor1, + account_investor4, + web3.utils.toWei("2", "ether"), + latestTime() + duration.days(1), + "ABC", + { from: accounts[j] } + ) + ); + + await I_ManualApprovalTransferManager.addManualApproval( + account_investor1, + account_investor4, + web3.utils.toWei("2", "ether"), + latestTime() + duration.days(1), + "ABC", + { from: token_owner } + ); + + await catchRevert(I_ManualApprovalTransferManager.revokeManualApproval(account_investor1, account_investor4, { + from: accounts[j] + }) + ); + + + console.log("Test number " + i + " with account " + j + " and perm " + randomPerms + " failed as expected"); + } + + await revertToSnapshot(snapId); + }; + }); + }); + +}); diff --git a/test/z_vesting_escrow_wallet.js b/test/z_vesting_escrow_wallet.js new file mode 100644 index 000000000..d15acddd1 --- /dev/null +++ b/test/z_vesting_escrow_wallet.js @@ -0,0 +1,1197 @@ +import {deployGPMAndVerifyed, deployVestingEscrowWalletAndVerifyed, setUpPolymathNetwork} from "./helpers/createInstances"; +import latestTime from "./helpers/latestTime"; +import {duration as durationUtil, latestBlock, promisifyLogWatch} from "./helpers/utils"; +import {catchRevert} from "./helpers/exceptions"; +import {increaseTime} from "./helpers/time"; +import {encodeModuleCall} from "./helpers/encodeCall"; + +const SecurityToken = artifacts.require('./SecurityToken.sol'); +const GeneralTransferManager = artifacts.require('./GeneralTransferManager'); +const GeneralPermissionManager = artifacts.require("./GeneralPermissionManager"); +const VestingEscrowWallet = artifacts.require('./VestingEscrowWallet.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('VestingEscrowWallet', accounts => { + + const CREATED = 0; + const STARTED = 1; + const COMPLETED = 2; + + // Accounts Variable declaration + let account_polymath; + let token_owner; + let wallet_admin; + let account_beneficiary1; + let account_beneficiary2; + let account_beneficiary3; + + let beneficiaries; + + let message = "Transaction Should Fail!"; + + // Contract Instance Declaration + let I_SecurityTokenRegistryProxy; + let I_GeneralPermissionManagerFactory; + let I_GeneralTransferManagerFactory; + let I_VestingEscrowWalletFactory; + let I_GeneralPermissionManager; + let I_VestingEscrowWallet; + let I_GeneralTransferManager; + let I_ModuleRegistryProxy; + let I_ModuleRegistry; + let I_FeatureRegistry; + let I_SecurityTokenRegistry; + let I_STRProxied; + let I_MRProxied; + let I_STFactory; + let I_SecurityToken; + let I_PolyToken; + let I_PolymathRegistry; + + // SecurityToken Details + const name = "Team"; + const symbol = "sap"; + const tokenDetails = "This is equity type of issuance"; + const decimals = 18; + const contact = "team@polymath.network"; + const delegateDetails = "Hello I am legit delegate"; + + // Module key + const delegateManagerKey = 1; + const transferManagerKey = 2; + const stoKey = 3; + + // Initial fee for ticker registry and security token registry + const initRegFee = web3.utils.toWei("250"); + + before(async () => { + // Accounts setup + account_polymath = accounts[0]; + token_owner = accounts[1]; + wallet_admin = accounts[2]; + + account_beneficiary1 = accounts[6]; + account_beneficiary2 = accounts[7]; + account_beneficiary3 = accounts[8]; + + beneficiaries = [ + account_beneficiary1, + account_beneficiary2, + account_beneficiary3 + ]; + + // 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 2: Deploy the GeneralDelegateManagerFactory + [I_GeneralPermissionManagerFactory] = await deployGPMAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // STEP 3: Deploy the VestingEscrowWallet + [I_VestingEscrowWalletFactory] = await deployVestingEscrowWalletAndVerifyed(account_polymath, I_MRProxied, I_PolyToken.address, 0); + + // Printing all the contract addresses + console.log(` + --------------------- Polymath Network Smart Contracts: --------------------- + PolymathRegistry: ${I_PolymathRegistry.address} + SecurityTokenRegistryProxy: ${I_SecurityTokenRegistryProxy.address} + SecurityTokenRegistry: ${I_SecurityTokenRegistry.address} + ModuleRegistry: ${I_ModuleRegistry.address} + ModuleRegistryProxy: ${I_ModuleRegistryProxy.address} + FeatureRegistry: ${I_FeatureRegistry.address} + + STFactory: ${I_STFactory.address} + GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address} + GeneralPermissionManagerFactory: ${I_GeneralPermissionManagerFactory.address} + + I_VestingEscrowWalletFactory: ${I_VestingEscrowWalletFactory.address} + ----------------------------------------------------------------------------- + `); + }); + + describe("Generate the SecurityToken", async() => { + + it("Should register the ticker before the generation of the security token", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let tx = await I_STRProxied.registerTicker(token_owner, symbol, contact, { from : token_owner }); + assert.equal(tx.logs[0].args._owner, token_owner); + assert.equal(tx.logs[0].args._ticker, symbol.toUpperCase()); + }); + + it("Should generate the new security token with the same symbol as registered above", async () => { + await I_PolyToken.approve(I_STRProxied.address, initRegFee, { from: token_owner }); + let _blockNo = latestBlock(); + let tx = await I_STRProxied.generateSecurityToken(name, symbol, tokenDetails, false, { from: token_owner }); + + // Verify the successful generation of the security token + assert.equal(tx.logs[1].args._ticker, symbol.toUpperCase(), "SecurityToken doesn't get deployed"); + + I_SecurityToken = SecurityToken.at(tx.logs[1].args._securityTokenAddress); + + const log = await promisifyLogWatch(I_SecurityToken.ModuleAdded({from: _blockNo}), 1); + + // Verify that GeneralTransferManager module get added successfully or not + assert.equal(log.args._types[0].toNumber(), 2); + assert.equal( + web3.utils.toAscii(log.args._name) + .replace(/\u0000/g, ''), + "GeneralTransferManager" + ); + }); + + it("Should intialize the auto attached modules", async () => { + let moduleData = (await I_SecurityToken.getModulesByType(2))[0]; + I_GeneralTransferManager = GeneralTransferManager.at(moduleData); + + }); + + it("Should successfully attach the General permission manager factory with the security token", async () => { + const tx = await I_SecurityToken.addModule(I_GeneralPermissionManagerFactory.address, "0x", 0, 0, { from: token_owner }); + assert.equal(tx.logs[2].args._types[0].toNumber(), delegateManagerKey, "General Permission Manager doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name).replace(/\u0000/g, ""), + "GeneralPermissionManager", + "GeneralPermissionManagerFactory module was not added" + ); + I_GeneralPermissionManager = GeneralPermissionManager.at(tx.logs[2].args._module); + }); + + it("Should successfully attach the VestingEscrowWallet with the security token", async () => { + let bytesData = encodeModuleCall( + ["address"], + [token_owner] + ); + + await I_SecurityToken.changeGranularity(1, {from: token_owner}); + const tx = await I_SecurityToken.addModule(I_VestingEscrowWalletFactory.address, bytesData, 0, 0, { from: token_owner }); + + assert.equal(tx.logs[2].args._types[0].toNumber(), 6, "VestingEscrowWallet doesn't get deployed"); + assert.equal( + web3.utils.toAscii(tx.logs[2].args._name) + .replace(/\u0000/g, ''), + "VestingEscrowWallet", + "VestingEscrowWallet module was not added" + ); + I_VestingEscrowWallet = VestingEscrowWallet.at(tx.logs[2].args._module); + }); + + it("Should Buy the tokens for token_owner", async() => { + // Add the Investor in to the whitelist + let tx = await I_GeneralTransferManager.modifyWhitelist( + token_owner, + latestTime(), + latestTime(), + latestTime() + durationUtil.days(10), + true, + { + from: token_owner, + gas: 6000000 + }); + + assert.equal(tx.logs[0].args._investor.toLowerCase(), token_owner.toLowerCase(), "Failed in adding the token_owner in whitelist"); + + // Mint some tokens + await I_SecurityToken.mint(token_owner, web3.utils.toWei('1', 'ether'), { from: token_owner }); + + assert.equal( + (await I_SecurityToken.balanceOf(token_owner)).toNumber(), + web3.utils.toWei('1', 'ether') + ); + + }); + + it("Should whitelist investors", async() => { + // Add the Investor in to the whitelist + let tx = await I_GeneralTransferManager.modifyWhitelistMulti( + [I_VestingEscrowWallet.address, account_beneficiary1, account_beneficiary2, account_beneficiary3], + [latestTime(), latestTime(), latestTime(), latestTime()], + [latestTime(), latestTime(), latestTime(), latestTime()], + [latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10), latestTime() + durationUtil.days(10)], + [true, true, true, true], + { + from: token_owner, + gas: 6000000 + }); + + assert.equal(tx.logs[0].args._investor, I_VestingEscrowWallet.address); + assert.equal(tx.logs[1].args._investor, account_beneficiary1); + assert.equal(tx.logs[2].args._investor, account_beneficiary2); + assert.equal(tx.logs[3].args._investor, account_beneficiary3); + }); + + it("Should successfully add the delegate", async() => { + let tx = await I_GeneralPermissionManager.addDelegate(wallet_admin, delegateDetails, { from: token_owner}); + assert.equal(tx.logs[0].args._delegate, wallet_admin); + }); + + it("Should provide the permission", async() => { + let tx = await I_GeneralPermissionManager.changePermission( + wallet_admin, + I_VestingEscrowWallet.address, + "ADMIN", + true, + {from: token_owner} + ); + assert.equal(tx.logs[0].args._delegate, wallet_admin); + }); + + it("Should get the permission", async () => { + let perm = await I_VestingEscrowWallet.getPermissions.call(); + assert.equal(web3.utils.toAscii(perm[0]).replace(/\u0000/g, ""), "ADMIN"); + }); + + it("Should get the tags of the factory", async () => { + let tags = await I_VestingEscrowWalletFactory.getTags.call(); + assert.equal(tags.length, 2); + assert.equal(web3.utils.toAscii(tags[0]).replace(/\u0000/g, ""), "Vested"); + assert.equal(web3.utils.toAscii(tags[1]).replace(/\u0000/g, ""), "Escrow Wallet"); + }); + + it("Should get the instructions of the factory", async () => { + assert.equal( + (await I_VestingEscrowWalletFactory.getInstructions.call()).replace(/\u0000/g, ""), + "Issuer can deposit tokens to the contract and create the vesting schedule for the given address (Affiliate/Employee). These address can withdraw tokens according to there vesting schedule." + ); + }); + + }); + + describe("Depositing and withdrawing tokens", async () => { + + it("Should not be able to change treasury wallet -- fail because address is invalid", async () => { + await catchRevert( + I_VestingEscrowWallet.changeTreasuryWallet(0, {from: token_owner}) + ); + }); + + it("Should not be able to deposit -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.changeTreasuryWallet(account_beneficiary1, {from: account_beneficiary1}) + ); + }); + + it("Should change treasury wallet", async () => { + const tx = await I_VestingEscrowWallet.changeTreasuryWallet(account_beneficiary1, {from: token_owner}); + + assert.equal(tx.logs[0].args._newWallet, account_beneficiary1); + assert.equal(tx.logs[0].args._oldWallet, token_owner); + let treasuryWallet = await I_VestingEscrowWallet.treasuryWallet.call(); + assert.equal(treasuryWallet, account_beneficiary1); + + await I_VestingEscrowWallet.changeTreasuryWallet(token_owner, {from: token_owner}); + }); + + it("Should fail to deposit zero amount of tokens", async () => { + await catchRevert( + I_VestingEscrowWallet.depositTokens(0, {from: token_owner}) + ); + }); + + it("Should not be able to deposit -- fail because of permissions check", async () => { + let numberOfTokens = 25000; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); + await catchRevert( + I_VestingEscrowWallet.depositTokens(25000, {from: account_beneficiary1}) + ); + }); + + it("Should deposit tokens for new vesting schedules", async () => { + let numberOfTokens = 25000; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); + const tx = await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); + + assert.equal(tx.logs[0].args._numberOfTokens, numberOfTokens); + + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + assert.equal(unassignedTokens, numberOfTokens); + + let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address); + assert.equal(balance.toNumber(), numberOfTokens); + }); + + it("Should not be able to withdraw tokens to a treasury -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.sendToTreasury(10, {from: account_beneficiary1}) + ); + }); + + it("Should not be able to withdraw tokens to a treasury -- fail because of zero amount", async () => { + await catchRevert( + I_VestingEscrowWallet.sendToTreasury(0, {from: wallet_admin}) + ); + }); + + it("Should not be able to withdraw tokens to a treasury -- fail because amount is greater than unassigned tokens", async () => { + let numberOfTokens = 25000 * 2; + await catchRevert( + I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_admin}) + ); + }); + + it("Should withdraw tokens to a treasury", async () => { + let numberOfTokens = 25000; + const tx = await I_VestingEscrowWallet.sendToTreasury(numberOfTokens, {from: wallet_admin}); + + assert.equal(tx.logs[0].args._numberOfTokens, numberOfTokens); + + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + assert.equal(unassignedTokens, 0); + + let balance = await I_SecurityToken.balanceOf.call(I_VestingEscrowWallet.address); + assert.equal(balance.toNumber(), 0); + }); + + it("Should not be able to push available tokens -- fail because of permissions check", async () => { + let templateName = "template-01"; + let numberOfTokens = 75000; + let duration = durationUtil.seconds(30); + let frequency = durationUtil.seconds(10); + let timeShift = durationUtil.seconds(100); + let startTime = latestTime() + timeShift; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); + await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); + await increaseTime(timeShift + frequency); + + await catchRevert( + I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: account_beneficiary1}) + ); + }); + + it("Should not be able to remove template -- fail because template is used", async () => { + await catchRevert( + I_VestingEscrowWallet.removeTemplate("template-01", {from: wallet_admin}) + ); + }); + + it("Should push available tokens to the beneficiary address", async () => { + let numberOfTokens = 75000; + const tx = await I_VestingEscrowWallet.pushAvailableTokens(account_beneficiary3, {from: wallet_admin}); + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); + assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens / 3); + + let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); + assert.equal(balance.toNumber(), numberOfTokens / 3); + + await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); + }); + + it("Should fail to modify vesting schedule -- fail because schedule already started", async () => { + let templateName = "template-01"; + let startTime = latestTime() + 100; + await catchRevert( + I_VestingEscrowWallet.modifySchedule(account_beneficiary3, templateName, startTime, {from: wallet_admin}) + ); + + await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + it("Should fail to modify vesting schedule -- fail because date in the past", async () => { + await catchRevert( + I_VestingEscrowWallet.modifySchedule(account_beneficiary3, "template-01", latestTime() - 1000, {from: wallet_admin}) + ); + }); + + it("Should withdraw available tokens to the beneficiary address", async () => { + let templateName = "template-02"; + let numberOfTokens = 33000; + let duration = durationUtil.seconds(30); + let frequency = durationUtil.seconds(10); + let timeShift = durationUtil.seconds(100); + let startTime = latestTime() + timeShift; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); + await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); + await increaseTime(timeShift + frequency * 3); + + const tx = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}); + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); + assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens); + + let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); + assert.equal(balance.toNumber(), numberOfTokens); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary3, templateName); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, COMPLETED); + + await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); + await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}); + }); + + it("Should withdraw available tokens 2 times by 3 schedules to the beneficiary address", async () => { + let schedules = [ + { + templateName: "template-1-01", + numberOfTokens: 100000, + duration: durationUtil.minutes(4), + frequency: durationUtil.minutes(1) + }, + { + templateName: "template-1-02", + numberOfTokens: 30000, + duration: durationUtil.minutes(6), + frequency: durationUtil.minutes(1) + }, + { + templateName: "template-1-03", + numberOfTokens: 2000, + duration: durationUtil.minutes(10), + frequency: durationUtil.minutes(1) + } + ]; + + let totalNumberOfTokens = getTotalNumberOfTokens(schedules); + await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); + for (let i = 0; i < schedules.length; i++) { + let templateName = schedules[i].templateName; + let numberOfTokens = schedules[i].numberOfTokens; + let duration = schedules[i].duration; + let frequency = schedules[i].frequency; + let startTime = latestTime() + durationUtil.seconds(100); + await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); + } + let stepCount = 6; + await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100)); + + let numberOfTokens = 100000 + (30000 / 6 * stepCount) + (2000 / 10 * stepCount); + const tx = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}); + + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); + assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), 100000); + assert.equal(tx.logs[1].args._beneficiary, account_beneficiary3); + assert.equal(tx.logs[1].args._numberOfTokens.toNumber(), 30000 / 6 * stepCount); + assert.equal(tx.logs[2].args._beneficiary, account_beneficiary3); + assert.equal(tx.logs[2].args._numberOfTokens.toNumber(), 2000 / 10 * stepCount); + + let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); + assert.equal(balance.toNumber(), numberOfTokens); + + stepCount = 4; + await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100)); + + const tx2 = await I_VestingEscrowWallet.pullAvailableTokens({from: account_beneficiary3}); + assert.equal(tx2.logs[0].args._beneficiary, account_beneficiary3); + assert.equal(tx2.logs[0].args._numberOfTokens.toNumber(), 2000 / 10 * stepCount); + + balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); + assert.equal(balance.toNumber(), totalNumberOfTokens); + + await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); + await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); + for (let i = 0; i < schedules.length; i++) { + await I_VestingEscrowWallet.removeTemplate(schedules[i].templateName, {from: wallet_admin}); + } + }); + + }); + + describe("Adding, modifying and revoking vesting schedule", async () => { + + let schedules = [ + { + templateName: "template-2-01", + numberOfTokens: 100000, + duration: durationUtil.years(4), + frequency: durationUtil.years(1), + startTime: latestTime() + durationUtil.days(1) + }, + { + templateName: "template-2-02", + numberOfTokens: 30000, + duration: durationUtil.weeks(6), + frequency: durationUtil.weeks(1), + startTime: latestTime() + durationUtil.days(2) + }, + { + templateName: "template-2-03", + numberOfTokens: 2000, + duration: durationUtil.days(10), + frequency: durationUtil.days(2), + startTime: latestTime() + durationUtil.days(3) + } + ]; + + it("Should fail to add vesting schedule to the beneficiary address -- fail because address in invalid", async () => { + await catchRevert( + I_VestingEscrowWallet.addSchedule(0, "template-2-01", 100000, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin}) + ); + }); + + it("Should fail to add vesting schedule to the beneficiary address -- fail because start date in the past", async () => { + await catchRevert( + I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 100000, 4, 1, latestTime() - durationUtil.days(1), {from: wallet_admin}) + ); + }); + + it("Should fail to add vesting schedule to the beneficiary address -- fail because number of tokens is 0", async () => { + await catchRevert( + I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 0, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin}) + ); + }); + + it("Should fail to add vesting schedule to the beneficiary address -- fail because duration can't be divided entirely by frequency", async () => { + await catchRevert( + I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 100000, 4, 3, latestTime() + durationUtil.days(1), {from: wallet_admin}) + ); + }); + + it("Should fail to add vesting schedule to the beneficiary address -- fail because number of tokens can't be divided entirely by period count", async () => { + await catchRevert( + I_VestingEscrowWallet.addSchedule(account_beneficiary1, "template-2-01", 5, 4, 1, latestTime() + durationUtil.days(1), {from: wallet_admin}) + ); + }); + + it("Should fail to get vesting schedule -- fail because address is invalid", async () => { + await catchRevert( + I_VestingEscrowWallet.getSchedule(0, "template-2-01") + ); + }); + + it("Should fail to get vesting schedule -- fail because schedule not found", async () => { + await catchRevert( + I_VestingEscrowWallet.getSchedule(account_beneficiary1, "template-2-01") + ); + }); + + it("Should fail to get count of vesting schedule -- fail because address is invalid", async () => { + await catchRevert( + I_VestingEscrowWallet.getScheduleCount(0) + ); + }); + + it("Should not be able to add schedule -- fail because of permissions check", async () => { + let templateName = schedules[0].templateName; + let numberOfTokens = schedules[0].numberOfTokens; + let duration = schedules[0].duration; + let frequency = schedules[0].frequency; + let startTime = schedules[0].startTime; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); + await catchRevert( + I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: account_beneficiary1}) + ); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + it("Should add vesting schedule to the beneficiary address", async () => { + let templateName = schedules[0].templateName; + let numberOfTokens = schedules[0].numberOfTokens; + let duration = schedules[0].duration; + let frequency = schedules[0].frequency; + let startTime = schedules[0].startTime; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); + const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); + + checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency); + checkScheduleLog(tx.logs[1], account_beneficiary1, templateName, startTime); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); + assert.equal(scheduleCount, 1); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); + + let templates = await I_VestingEscrowWallet.getTemplateNames.call(account_beneficiary1); + assert.equal(web3.utils.hexToUtf8(templates[0]), templateName); + }); + + it("Should add vesting schedule without depositing to the beneficiary address", async () => { + let templateName = "template-2-01-2"; + let numberOfTokens = schedules[0].numberOfTokens; + let duration = schedules[0].duration; + let frequency = schedules[0].frequency; + let startTime = schedules[0].startTime; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, {from: token_owner}); + const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary1, templateName, numberOfTokens, duration, frequency, startTime, {from: token_owner}); + + checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency); + assert.equal(tx.logs[1].args._numberOfTokens, numberOfTokens); + checkScheduleLog(tx.logs[2], account_beneficiary1, templateName, startTime); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); + assert.equal(scheduleCount, 2); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); + + await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + it("Should fail to modify vesting schedule -- fail because schedule not found", async () => { + let templateName = "template-2-03"; + let startTime = schedules[0].startTime; + await catchRevert( + I_VestingEscrowWallet.modifySchedule(account_beneficiary1, templateName, startTime, {from: wallet_admin}) + ); + }); + + it("Should not be able to modify schedule -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.modifySchedule(account_beneficiary1, "template-2-01", latestTime() + 100, {from: account_beneficiary1}) + ); + }); + + it("Should modify vesting schedule for the beneficiary's address", async () => { + let templateName = "template-2-01"; + let numberOfTokens = schedules[0].numberOfTokens; + let duration = schedules[0].duration; + let frequency = schedules[0].frequency; + let startTime = schedules[1].startTime; + const tx = await I_VestingEscrowWallet.modifySchedule(account_beneficiary1, templateName, startTime, {from: wallet_admin}); + + checkScheduleLog(tx.logs[0], account_beneficiary1, templateName, startTime); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); + assert.equal(scheduleCount.toNumber(), 1); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, "template-2-01"); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); + }); + + it("Should not be able to revoke schedule -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, "template-2-01", {from: account_beneficiary1}) + ); + }); + + it("Should revoke vesting schedule from the beneficiary address", async () => { + let templateName = "template-2-01"; + const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary1); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), templateName); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); + assert.equal(scheduleCount, 0); + + await I_VestingEscrowWallet.removeTemplate(templateName, {from: wallet_admin}) + }); + + it("Should fail to revoke vesting schedule -- fail because address is invalid", async () => { + await catchRevert( + I_VestingEscrowWallet.revokeSchedule(0, "template-2-01", {from: wallet_admin}) + ); + }); + + it("Should fail to revoke vesting schedule -- fail because schedule not found", async () => { + await catchRevert( + I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, "template-2-02", {from: wallet_admin}) + ); + }); + + it("Should fail to revoke vesting schedules -- fail because address is invalid", async () => { + await catchRevert( + I_VestingEscrowWallet.revokeAllSchedules(0, {from: wallet_admin}) + ); + }); + + it("Should add 3 vesting schedules to the beneficiary address", async () => { + let totalNumberOfTokens = getTotalNumberOfTokens(schedules); + await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); + for (let i = 0; i < schedules.length; i++) { + let templateName = schedules[i].templateName; + let numberOfTokens = schedules[i].numberOfTokens; + let duration = schedules[i].duration; + let frequency = schedules[i].frequency; + let startTime = schedules[i].startTime; + const tx = await I_VestingEscrowWallet.addSchedule(account_beneficiary2, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); + + checkTemplateLog(tx.logs[0], templateName, numberOfTokens, duration, frequency); + checkScheduleLog(tx.logs[1], account_beneficiary2, templateName, startTime); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2); + assert.equal(scheduleCount, i + 1); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary2, templateName); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); + } + }); + + it("Should not be able to revoke schedules -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary1, {from: account_beneficiary1}) + ); + }); + + it("Should revoke 1 of 3 vesting schedule from the beneficiary address", async () => { + let templateName = schedules[1].templateName; + const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary2, templateName, {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary2); + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._templateName), templateName); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2); + assert.equal(scheduleCount, 2); + }); + + it("Should revoke 2 vesting schedules from the beneficiary address", async () => { + const tx = await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary2, {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary2); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary2); + assert.equal(scheduleCount, 0); + }); + + it("Should push available tokens during revoking vesting schedule", async () => { + let schedules = [ + { + templateName: "template-3-01", + numberOfTokens: 100000, + duration: durationUtil.minutes(4), + frequency: durationUtil.minutes(1) + }, + { + templateName: "template-3-02", + numberOfTokens: 30000, + duration: durationUtil.minutes(6), + frequency: durationUtil.minutes(1) + }, + { + templateName: "template-3-03", + numberOfTokens: 2000, + duration: durationUtil.minutes(10), + frequency: durationUtil.minutes(1) + } + ]; + + let totalNumberOfTokens = getTotalNumberOfTokens(schedules); + await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); + for (let i = 0; i < schedules.length; i++) { + let templateName = schedules[i].templateName; + let numberOfTokens = schedules[i].numberOfTokens; + let duration = schedules[i].duration; + let frequency = schedules[i].frequency; + let startTime = latestTime() + durationUtil.seconds(100); + await I_VestingEscrowWallet.addSchedule(account_beneficiary3, templateName, numberOfTokens, duration, frequency, startTime, {from: wallet_admin}); + } + let stepCount = 3; + await increaseTime(durationUtil.minutes(stepCount) + durationUtil.seconds(100)); + + const tx = await I_VestingEscrowWallet.revokeSchedule(account_beneficiary3, "template-3-01", {from: wallet_admin}); + assert.equal(tx.logs[0].args._beneficiary, account_beneficiary3); + assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), 100000 / 4 * stepCount); + + let balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); + assert.equal(balance.toNumber(), 100000 / 4 * stepCount); + + stepCount = 7; + await increaseTime(durationUtil.minutes(stepCount)); + + const tx2 = await I_VestingEscrowWallet.revokeAllSchedules(account_beneficiary3, {from: wallet_admin}); + assert.equal(tx2.logs[0].args._beneficiary, account_beneficiary3); + assert.equal(tx2.logs[0].args._numberOfTokens.toNumber(), 2000); + assert.equal(tx2.logs[1].args._beneficiary, account_beneficiary3); + assert.equal(tx2.logs[1].args._numberOfTokens.toNumber(), 30000); + + for (let i = 0; i < schedules.length; i++) { + await I_VestingEscrowWallet.removeTemplate(schedules[i].templateName, {from: wallet_admin}); + } + + balance = await I_SecurityToken.balanceOf.call(account_beneficiary3); + assert.equal(balance.toNumber(), totalNumberOfTokens - 100000 / 4); + + await I_SecurityToken.transfer(token_owner, balance, {from: account_beneficiary3}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + }); + + describe("Adding, using and removing templates", async () => { + + let schedules = [ + { + templateName: "template-4-01", + numberOfTokens: 100000, + duration: durationUtil.years(4), + frequency: durationUtil.years(1), + startTime: latestTime() + durationUtil.days(1) + }, + { + templateName: "template-4-02", + numberOfTokens: 30000, + duration: durationUtil.weeks(6), + frequency: durationUtil.weeks(1), + startTime: latestTime() + durationUtil.days(2) + }, + { + templateName: "template-4-03", + numberOfTokens: 2000, + duration: durationUtil.days(10), + frequency: durationUtil.days(2), + startTime: latestTime() + durationUtil.days(3) + } + ]; + + it("Should not be able to add template -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.addTemplate("template-4-01", 25000, 4, 1, {from: account_beneficiary1}) + ); + }); + + it("Should not be able to add template -- fail because of invalid name", async () => { + await catchRevert( + I_VestingEscrowWallet.addTemplate("", 25000, 4, 1, {from: wallet_admin}) + ); + }); + + it("Should add 3 Templates", async () => { + let oldTemplateCount = await I_VestingEscrowWallet.getTemplateCount.call(); + for (let i = 0; i < schedules.length; i++) { + let templateName = schedules[i].templateName; + let numberOfTokens = schedules[i].numberOfTokens; + let duration = schedules[i].duration; + let frequency = schedules[i].frequency; + const tx = await I_VestingEscrowWallet.addTemplate(templateName, numberOfTokens, duration, frequency, {from: wallet_admin}); + + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), templateName); + assert.equal(tx.logs[0].args._numberOfTokens.toNumber(), numberOfTokens); + assert.equal(tx.logs[0].args._duration.toNumber(), duration); + assert.equal(tx.logs[0].args._frequency.toNumber(), frequency); + } + let templateNames = await I_VestingEscrowWallet.getAllTemplateNames.call(); + + for (let i = 0, j = oldTemplateCount; i < schedules.length; i++, j++) { + assert.equal(web3.utils.hexToUtf8(templateNames[j]), schedules[i].templateName); + } + }); + + it("Should not be able to add template -- fail because template already exists", async () => { + await catchRevert( + I_VestingEscrowWallet.addTemplate("template-4-01", 25000, 4, 1, {from: wallet_admin}) + ); + }); + + it("Should not be able to remove template -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.removeTemplate("template-4-02", {from: account_beneficiary1}) + ); + }); + + it("Should not be able to remove template -- fail because template not found", async () => { + await catchRevert( + I_VestingEscrowWallet.removeTemplate("template-444-02", {from: wallet_admin}) + ); + }); + + it("Should remove template", async () => { + const tx = await I_VestingEscrowWallet.removeTemplate("template-4-02", {from: wallet_admin}); + + assert.equal(web3.utils.hexToUtf8(tx.logs[0].args._name), "template-4-02"); + }); + + it("Should fail to add vesting schedule from template -- fail because template not found", async () => { + let startTime = schedules[2].startTime; + await catchRevert( + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-4-02", startTime, {from: wallet_admin}) + ); + }); + + it("Should not be able to add schedule from template -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-4-01", latestTime(), {from: account_beneficiary1}) + ); + }); + + it("Should not be able to add vesting schedule from template -- fail because template not found", async () => { + await catchRevert( + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, "template-777", latestTime() + 100, {from: wallet_admin}) + ); + }); + + it("Should add vesting schedule from template", async () => { + let templateName = schedules[2].templateName; + let numberOfTokens = schedules[2].numberOfTokens; + let duration = schedules[2].duration; + let frequency = schedules[2].frequency; + let startTime = schedules[2].startTime; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, numberOfTokens, { from: token_owner }); + await I_VestingEscrowWallet.depositTokens(numberOfTokens, {from: token_owner}); + const tx = await I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, startTime, {from: wallet_admin}); + + checkScheduleLog(tx.logs[0], account_beneficiary1, templateName, startTime); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(account_beneficiary1); + assert.equal(scheduleCount, 1); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(account_beneficiary1, templateName); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, CREATED); + + await I_VestingEscrowWallet.revokeSchedule(account_beneficiary1, templateName, {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + it("Should not be able to add vesting schedule from template -- fail because template already added", async () => { + let templateName = schedules[2].templateName; + await catchRevert( + I_VestingEscrowWallet.addScheduleFromTemplate(account_beneficiary1, templateName, latestTime() + 100, {from: wallet_admin}) + ); + }); + + it("Should fail to remove template", async () => { + await catchRevert( + I_VestingEscrowWallet.removeTemplate("template-4-02", {from: wallet_admin}) + ); + }); + + it("Should remove 2 Templates", async () => { + let templateCount = await I_VestingEscrowWallet.getTemplateCount.call({from: wallet_admin}); + + await I_VestingEscrowWallet.removeTemplate("template-4-01", {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate("template-4-03", {from: wallet_admin}); + + let templateCountAfterRemoving = await I_VestingEscrowWallet.getTemplateCount.call({from: wallet_admin}); + assert.equal(templateCount - templateCountAfterRemoving, 2); + }); + + }); + + describe("Tests for multi operations", async () => { + + let templateNames = ["template-5-01", "template-5-02", "template-5-03"]; + + it("Should not be able to add schedules to the beneficiaries -- fail because of permissions check", async () => { + let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100]; + await catchRevert( + I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, [10000, 10000, 10000], [4, 4, 4], [1, 1, 1], startTimes, {from: account_beneficiary1}) + ); + }); + + it("Should not be able to add schedules to the beneficiaries -- fail because of arrays sizes mismatch", async () => { + let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100]; + let totalNumberOfTokens = 60000; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); + await catchRevert( + I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, [20000, 30000, 10000], [4, 4], [1, 1, 1], startTimes, {from: wallet_admin}) + ); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + it("Should add schedules for 3 beneficiaries", async () => { + let numberOfTokens = [15000, 15000, 15000]; + let durations = [durationUtil.seconds(50), durationUtil.seconds(50), durationUtil.seconds(50)]; + let frequencies = [durationUtil.seconds(10), durationUtil.seconds(10), durationUtil.seconds(10)]; + let timeShift = durationUtil.seconds(100); + let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + + let totalNumberOfTokens = 60000; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); + + let tx = await I_VestingEscrowWallet.addScheduleMulti(beneficiaries, templateNames, numberOfTokens, durations, frequencies, startTimes, {from: wallet_admin}); + + for (let i = 0; i < beneficiaries.length; i++) { + let templateName = templateNames[i]; + let beneficiary = beneficiaries[i]; + checkTemplateLog(tx.logs[i* 2], templateName, numberOfTokens[i], durations[i], frequencies[i]); + checkScheduleLog(tx.logs[i * 2 + 1], beneficiary, templateName, startTimes[i]); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(beneficiary); + assert.equal(scheduleCount, 1); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(beneficiary, templateName); + checkSchedule(schedule, numberOfTokens[i], durations[i], frequencies[i], startTimes[i], CREATED); + } + }); + + it("Should not be able modify vesting schedule for 3 beneficiary's addresses -- fail because of arrays sizes mismatch", async () => { + let timeShift = durationUtil.seconds(100); + let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + + await catchRevert( + I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, ["template-5-01"], startTimes, {from: wallet_admin}) + ); + }); + + it("Should not be able to modify schedules for the beneficiaries -- fail because of permissions check", async () => { + let timeShift = durationUtil.seconds(100); + let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + + await catchRevert( + I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, templateNames, startTimes, {from: account_beneficiary1}) + ); + }); + + it("Should modify vesting schedule for 3 beneficiary's addresses", async () => { + let numberOfTokens = [15000, 15000, 15000]; + let durations = [durationUtil.seconds(50), durationUtil.seconds(50), durationUtil.seconds(50)]; + let frequencies = [durationUtil.seconds(10), durationUtil.seconds(10), durationUtil.seconds(10)]; + let timeShift = durationUtil.seconds(100); + let startTimes = [latestTime() + timeShift, latestTime() + timeShift, latestTime() + timeShift]; + + const tx = await I_VestingEscrowWallet.modifyScheduleMulti(beneficiaries, templateNames, startTimes, {from: wallet_admin}); + await increaseTime(timeShift + frequencies[0]); + + for (let i = 0; i < beneficiaries.length; i++) { + let log = tx.logs[i]; + let beneficiary = beneficiaries[i]; + checkScheduleLog(log, beneficiary, templateNames[i], startTimes[i]); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(beneficiary); + assert.equal(scheduleCount, 1); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(beneficiary, templateNames[i]); + checkSchedule(schedule, numberOfTokens[i], durations[i], frequencies[i], startTimes[i], STARTED); + } + }); + + it("Should not be able to send available tokens to the beneficiaries addresses -- fail because of array size", async () => { + await catchRevert( + I_VestingEscrowWallet.pushAvailableTokensMulti(0, 3, {from: wallet_admin}) + ); + }); + + it("Should not be able to send available tokens to the beneficiaries -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: account_beneficiary1}) + ); + }); + + it("Should send available tokens to the beneficiaries addresses", async () => { + const tx = await I_VestingEscrowWallet.pushAvailableTokensMulti(0, 2, {from: wallet_admin}); + + for (let i = 0; i < beneficiaries.length; i++) { + let log = tx.logs[i]; + let beneficiary = beneficiaries[i]; + assert.equal(log.args._numberOfTokens.toNumber(), 3000); + + let balance = await I_SecurityToken.balanceOf.call(beneficiary); + assert.equal(balance.toNumber(), 3000); + + await I_SecurityToken.transfer(token_owner, balance, {from: beneficiary}); + await I_VestingEscrowWallet.revokeAllSchedules(beneficiary, {from: wallet_admin}); + await I_VestingEscrowWallet.removeTemplate(templateNames[i], {from: wallet_admin}); + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + } + }); + + it("Should not be able to add schedules from template to the beneficiaries -- fail because of permissions check", async () => { + let templateName = "template-6-01"; + let numberOfTokens = 18000; + let duration = durationUtil.weeks(3); + let frequency = durationUtil.weeks(1); + let templateNames = [templateName, templateName, templateName]; + let startTimes = [latestTime() + durationUtil.seconds(100), latestTime() + durationUtil.seconds(100), latestTime() + durationUtil.seconds(100)]; + + let totalNumberOfTokens = numberOfTokens * 3; + await I_SecurityToken.approve(I_VestingEscrowWallet.address, totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.depositTokens(totalNumberOfTokens, {from: token_owner}); + await I_VestingEscrowWallet.addTemplate(templateName, numberOfTokens, duration, frequency, {from: wallet_admin}); + + await catchRevert( + I_VestingEscrowWallet.addScheduleFromTemplateMulti(beneficiaries, templateNames, startTimes, {from: account_beneficiary1}) + ); + }); + + it("Should add schedules from template for 3 beneficiaries", async () => { + let templateName = "template-6-01"; + let numberOfTokens = 18000; + let duration = durationUtil.weeks(3); + let frequency = durationUtil.weeks(1); + let templateNames = [templateName, templateName, templateName]; + let startTimes = [latestTime() + 100, latestTime() + 100, latestTime() + 100]; + + let tx = await I_VestingEscrowWallet.addScheduleFromTemplateMulti(beneficiaries, templateNames, startTimes, {from: wallet_admin}); + for (let i = 0; i < beneficiaries.length; i++) { + let log = tx.logs[i]; + let beneficiary = beneficiaries[i]; + checkScheduleLog(log, beneficiary, templateName, startTimes[i]); + + let schedule = await I_VestingEscrowWallet.getSchedule.call(beneficiary, templateName); + checkSchedule(schedule, numberOfTokens, duration, frequency, startTimes[i], CREATED); + } + }); + + it("Should not be able to revoke schedules of the beneficiaries -- fail because of permissions check", async () => { + await catchRevert( + I_VestingEscrowWallet.revokeSchedulesMulti(beneficiaries, {from: account_beneficiary1}) + ); + }); + + it("Should revoke vesting schedule from the 3 beneficiary's addresses", async () => { + const tx = await I_VestingEscrowWallet.revokeSchedulesMulti(beneficiaries, {from: wallet_admin}); + + for (let i = 0; i < beneficiaries.length; i++) { + let log = tx.logs[i]; + let beneficiary = beneficiaries[i]; + assert.equal(log.args._beneficiary, beneficiary); + + let scheduleCount = await I_VestingEscrowWallet.getScheduleCount.call(beneficiary); + assert.equal(scheduleCount, 0); + } + + let unassignedTokens = await I_VestingEscrowWallet.unassignedTokens.call(); + await I_VestingEscrowWallet.sendToTreasury(unassignedTokens, {from: wallet_admin}); + }); + + }); + +}); + +function checkTemplateLog(log, templateName, numberOfTokens, duration, frequency) { + assert.equal(web3.utils.hexToUtf8(log.args._name), templateName); + assert.equal(log.args._numberOfTokens.toNumber(), numberOfTokens); + assert.equal(log.args._duration.toNumber(), duration); + assert.equal(log.args._frequency.toNumber(), frequency); +} + +function checkScheduleLog(log, beneficiary, templateName, startTime) { + assert.equal(log.args._beneficiary, beneficiary); + assert.equal(web3.utils.hexToUtf8(log.args._templateName), templateName); + assert.equal(log.args._startTime.toNumber(), startTime); +} + +function checkSchedule(schedule, numberOfTokens, duration, frequency, startTime, state) { + assert.equal(schedule[0].toNumber(), numberOfTokens); + assert.equal(schedule[1].toNumber(), duration); + assert.equal(schedule[2].toNumber(), frequency); + assert.equal(schedule[3].toNumber(), startTime); + assert.equal(schedule[5].toNumber(), state); +} + +function getTotalNumberOfTokens(schedules) { + let numberOfTokens = 0; + for (let i = 0; i < schedules.length; i++) { + numberOfTokens += schedules[i].numberOfTokens; + } + return numberOfTokens; +} diff --git a/truffle-ci.js b/truffle-ci.js index f427eb332..f6d2bf7ae 100644 --- a/truffle-ci.js +++ b/truffle-ci.js @@ -1,6 +1,8 @@ require('babel-register'); require('babel-polyfill'); +let HDWalletProvider = require("truffle-hdwallet-provider"); + module.exports = { networks: { development: { @@ -9,6 +11,13 @@ module.exports = { network_id: '*', // Match any network id gas: 7900000, }, + kovan: { + provider: () => { + return new HDWalletProvider(process.env.PRIVATE_KEY, "https://kovan.mudit.blog/"); + }, + network_id: '42', + gasPrice: 2000000000 + }, coverage: { host: "localhost", network_id: "*", @@ -17,6 +26,17 @@ module.exports = { gasPrice: 0x01 // <-- Use this low gas price } }, + compilers: { + solc: { + version: "native", + settings: { + optimizer: { + enabled: true, + runs: 200 + } + } + } + }, solc: { optimizer: { enabled: true, @@ -24,10 +44,6 @@ module.exports = { }, }, mocha: { - enableTimeouts: false, - reporter: "mocha-junit-reporter", - reporterOptions: { - mochaFile: './test-results/mocha/results.xml' - } + enableTimeouts: false } }; diff --git a/yarn.lock b/yarn.lock index f9dc37309..cd5c67fbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,40 +5,20 @@ "@babel/code-frame@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" - integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== dependencies: "@babel/highlight" "^7.0.0" "@babel/highlight@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" - integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== dependencies: chalk "^2.0.0" esutils "^2.0.2" js-tokens "^4.0.0" -"@soldoc/markdown@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@soldoc/markdown/-/markdown-0.1.0.tgz#9f85be75049af9721b5129f133d52dafbf5f671e" - -"@soldoc/soldoc@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@soldoc/soldoc/-/soldoc-0.4.3.tgz#24ffee9264228e1c3edd61fd3162d63587954933" - dependencies: - "@soldoc/markdown" "^0.1.0" - chalk "^2.3.1" - deep-assign "^2.0.0" - fs-extra "^5.0.0" - shelljs "^0.8.1" - solc "^0.4.19" - valid-url "^1.0.9" - yargs "^11.0.0" - "@types/node@^10.3.2": - version "10.12.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.2.tgz#d77f9faa027cadad9c912cd47f4f8b07b0fb0864" - integrity sha512-53ElVDSnZeFUUFIYzI8WLQ25IhWzb6vbddNp8UHlXQyU0ET2RhV5zg0NfubzU7iNMh5bBXb0htCzfvrSVNgzaQ== + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" abbrev@1: version "1.1.1" @@ -67,43 +47,23 @@ accepts@~1.3.5: mime-types "~2.1.18" negotiator "0.6.1" -acorn-dynamic-import@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" - dependencies: - acorn "^4.0.3" - acorn-jsx@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" - integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== - -acorn@^4.0.3: - version "4.0.13" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" -acorn@^5.0.0: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - -acorn@^6.0.7: - version "6.1.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" - integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== +acorn@^6.0.2: + version "6.0.5" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.5.tgz#81730c0815f3f3b34d8efa95cb7430965f4d887a" aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" aes-js@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.1.tgz#89fd1f94ae51b4c72d62466adc1a7323ff52f072" - -ajv-keywords@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + version "3.1.2" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a" -ajv@^5.1.1, ajv@^5.2.2, ajv@^5.3.0: +ajv@^5.2.2: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -112,33 +72,15 @@ ajv@^5.1.1, ajv@^5.2.2, ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.1.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.4.tgz#247d5274110db653706b550fcc2b797ca28cfc59" - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.9.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" - integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== +ajv@^6.5.3, ajv@^6.5.5, ajv@^6.6.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d" dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - ambi@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/ambi/-/ambi-2.5.0.tgz#7c8e372be48891157e7cea01cb6f9143d1f74220" @@ -166,7 +108,6 @@ ansi-regex@^3.0.0: ansi-regex@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" - integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== ansi-styles@^2.2.1: version "2.2.1" @@ -189,13 +130,6 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -213,13 +147,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -arguments-extended@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/arguments-extended/-/arguments-extended-0.0.3.tgz#6107e4917d0eb6f0a4dd66320fc15afc72ef4946" - dependencies: - extended "~0.0.3" - is-extended "~0.0.8" - arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -238,14 +165,6 @@ arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" -array-extended@~0.0.3, array-extended@~0.0.4, array-extended@~0.0.5: - version "0.0.11" - resolved "https://registry.yarnpkg.com/array-extended/-/array-extended-0.0.11.tgz#d7144ae748de93ca726f121009dbff1626d164bd" - dependencies: - arguments-extended "~0.0.3" - extended "~0.0.3" - is-extended "~0.0.3" - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -276,12 +195,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -289,7 +202,6 @@ assign-symbols@^1.0.0: astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-each@^1.0.0: version "1.0.1" @@ -321,6 +233,10 @@ async@^2.0.1, async@^2.1.2, async@^2.4.0, async@^2.4.1, async@^2.5.0: dependencies: lodash "^4.17.10" +async@~0.9.0: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + async@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" @@ -938,8 +854,8 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" base-x@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77" + version "3.0.5" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.5.tgz#d3ada59afed05b921ab581ec3112e6444ba0795a" dependencies: safe-buffer "^5.0.1" @@ -969,23 +885,17 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" - bignumber.js@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-5.0.0.tgz#fbce63f09776b3000a83185badcde525daf34833" - integrity sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg== bignumber.js@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.1.0.tgz#db6f14067c140bd46624815a7916c92d9b6c24b1" -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@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.1.tgz#5d419191370fb558c64e3e5f70d68e5947138832" "bignumber.js@git+https://github.com/debris/bignumber.js#master": version "2.0.7" @@ -1004,8 +914,14 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" bindings@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" + version "1.3.1" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" + +bindings@^1.3.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.4.0.tgz#909efa49f2ebe07ecd3cb136778f665052040127" + dependencies: + file-uri-to-path "1.0.0" bip66@^1.1.3: version "1.1.5" @@ -1024,12 +940,23 @@ bitcore-lib@^0.15.0: inherits "=2.0.1" lodash "=4.17.4" +bitcore-lib@^0.16.0: + version "0.16.0" + resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.16.0.tgz#a2c3ec1108cdb90386f728282ab833e0c77c9533" + dependencies: + bn.js "=4.11.8" + bs58 "=4.0.1" + buffer-compare "=1.1.1" + elliptic "=6.4.0" + inherits "=2.0.1" + lodash "=4.17.11" + bitcore-mnemonic@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bitcore-mnemonic/-/bitcore-mnemonic-1.5.0.tgz#c7e785beb6bf0616ed4992785dc3658670425a39" + version "1.7.0" + resolved "https://registry.yarnpkg.com/bitcore-mnemonic/-/bitcore-mnemonic-1.7.0.tgz#253295a773135e1a0b455871de614996afc8f5e1" dependencies: - bitcore-lib "^0.15.0" - unorm "^1.3.3" + bitcore-lib "^0.16.0" + unorm "^1.4.1" bl@^1.0.0: version "1.2.2" @@ -1048,15 +975,15 @@ bluebird@^2.9.34: version "2.11.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" -bluebird@^3.4.6, bluebird@^3.5.0: - version "3.5.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a" +bluebird@^3.4.6, bluebird@^3.5.0, bluebird@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" -bn.js@=4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.3, bn.js@^4.11.6, bn.js@^4.4.0, bn.js@^4.8.0: +bn.js@4.11.8, bn.js@=4.11.8, bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.3, bn.js@^4.11.6, bn.js@^4.4.0, bn.js@^4.8.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1080,13 +1007,14 @@ body-parser@1.18.3, body-parser@^1.16.0: type-is "~1.6.16" borc@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/borc/-/borc-2.0.3.tgz#08845ea73a6d3211120928ee3929f8dc2de9f52e" + version "2.1.0" + resolved "https://registry.yarnpkg.com/borc/-/borc-2.1.0.tgz#2def2fc69868633b965a9750e7f210d778190303" dependencies: - bignumber.js "^6.0.0" + 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" @@ -1103,7 +1031,7 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.0, braces@^2.3.1: +braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: @@ -1165,11 +1093,12 @@ browserify-rsa@^4.0.0: bn.js "^4.1.0" randombytes "^2.0.1" -browserify-sha3@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/browserify-sha3/-/browserify-sha3-0.0.1.tgz#3ff34a3006ef15c0fb3567e541b91a2340123d11" +browserify-sha3@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/browserify-sha3/-/browserify-sha3-0.0.4.tgz#086c47b8c82316c9d47022c26185954576dd8e26" dependencies: - js-sha3 "^0.3.1" + js-sha3 "^0.6.1" + safe-buffer "^5.1.1" browserify-sign@^4.0.0: version "4.0.4" @@ -1183,12 +1112,6 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - dependencies: - pako "~1.0.5" - browserslist@^3.2.6: version "3.2.8" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" @@ -1261,7 +1184,7 @@ buffer@^3.0.1: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^4.3.0, buffer@^4.9.0: +buffer@^4.9.0: version "4.9.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" dependencies: @@ -1280,10 +1203,6 @@ builtin-modules@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -1307,10 +1226,6 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -1327,20 +1242,13 @@ caminte@0.3.7: uuid "^3.0.1" caniuse-lite@^1.0.30000844: - version "1.0.30000890" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000890.tgz#86a18ffcc65d79ec6a437e985761b8bf1c4efeaf" + version "1.0.30000926" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000926.tgz#4361a99d818ca6e521dbe89a732de62a194a789c" caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1351,7 +1259,7 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -1359,24 +1267,13 @@ chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= checkpoint-store@^1.1.0: version "1.1.0" @@ -1399,26 +1296,7 @@ chokidar@^1.6.0: optionalDependencies: fsevents "^1.0.0" -chokidar@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" - dependencies: - anymatch "^2.0.0" - async-each "^1.0.0" - braces "^2.3.0" - glob-parent "^3.1.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - lodash.debounce "^4.0.8" - normalize-path "^2.1.1" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - upath "^1.0.5" - optionalDependencies: - fsevents "^1.2.2" - -chownr@^1.0.1: +chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -1438,9 +1316,9 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -cli-color@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.3.0.tgz#cd2ec212efbd1a0eeb5b017f17d4e2d15e91420f" +cli-color@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" dependencies: ansi-regex "^2.1.1" d "1" @@ -1459,14 +1337,6 @@ cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - cliui@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -1524,8 +1394,8 @@ colors@1.0.x: resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" colors@^1.1.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" + version "1.3.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.7" @@ -1541,7 +1411,7 @@ commander@2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" -commander@^2.11.0, commander@^2.14.1, commander@^2.15.0, commander@^2.8.1, commander@^2.9.0: +commander@^2.14.1, commander@^2.15.0, commander@^2.19.0, commander@^2.8.1, commander@^2.9.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -1567,20 +1437,10 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -1616,21 +1476,21 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" core-js@^2.4.0, core-js@^2.5.0: - version "2.5.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" + version "2.6.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.1.tgz#87416ae817de957a3f249b3b5ca475d4aaed6042" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" cors@^2.8.1: - version "2.8.4" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686" + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" dependencies: object-assign "^4" vary "^1" -coveralls@^3.0.1: +coveralls@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.2.tgz#f5a0bcd90ca4e64e088b710fa8dda640aea4884f" dependencies: @@ -1669,12 +1529,12 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cron-parser@^2.4.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.6.0.tgz#ae2514ceda9ccb540256e201bdd23ae814e03674" +cron-parser@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651" dependencies: is-nan "^1.2.1" - moment-timezone "^0.5.0" + moment-timezone "^0.5.23" cross-spawn@^5.0.1: version "5.1.0" @@ -1687,7 +1547,6 @@ cross-spawn@^5.0.1: cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" path-key "^2.0.1" @@ -1698,9 +1557,8 @@ cross-spawn@^6.0.5: crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= -crypto-browserify@3.12.0, crypto-browserify@^3.11.0: +crypto-browserify@3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" dependencies: @@ -1720,10 +1578,6 @@ crypto-js@^3.1.4, crypto-js@^3.1.5: version "3.1.8" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5" -crypto-js@^3.1.9-1: - version "3.1.9-1" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" - crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -1732,6 +1586,10 @@ csextends@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/csextends/-/csextends-1.2.0.tgz#6374b210984b54d4495f29c99d3dd069b80543e5" +csv-parse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.3.0.tgz#e27cc1c4c5426201a896389c9f97273a0d47dad4" + cycle@1.0.x: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" @@ -1748,25 +1606,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-extended@~0.0.3: - version "0.0.6" - resolved "https://registry.yarnpkg.com/date-extended/-/date-extended-0.0.6.tgz#23802d57dd1bf7818813fe0c32e851a86da267c9" - dependencies: - array-extended "~0.0.3" - extended "~0.0.3" - is-extended "~0.0.3" - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - death@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/death/-/death-1.1.0.tgz#01aa9c401edd92750514470b8266390c66c67318" debug@*, debug@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" dependencies: ms "^2.1.1" @@ -1782,20 +1628,16 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0: +debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" dependencies: ms "^2.1.1" -decamelize@^1.0.0, decamelize@^1.1.1: +decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" -declare.js@~0.0.4: - version "0.0.8" - resolved "https://registry.yarnpkg.com/declare.js/-/declare.js-0.0.8.tgz#0478adff9564c004f51df73d8bc134019d28dcde" - decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -1854,11 +1696,9 @@ decompress@^4.0.0: pify "^2.3.0" strip-dirs "^2.0.0" -deep-assign@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-2.0.0.tgz#ebe06b1f07f08dae597620e3dd1622f371a1c572" - dependencies: - is-obj "^1.0.0" +deep-equal@~0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-0.2.2.tgz#84b745896f34c684e98f2ce0e42abaf43bba017d" deep-equal@~1.0.1: version "1.0.1" @@ -1948,7 +1788,7 @@ diff@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" -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" @@ -1978,10 +1818,6 @@ dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - drbg.js@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b" @@ -2007,24 +1843,24 @@ 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" -editions@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/editions/-/editions-2.0.2.tgz#54fdac6fb24b0a1a72ffc1ba0126c10602c3e0bd" +editions@^2.1.0, editions@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/editions/-/editions-2.1.3.tgz#727ccf3ec2c7b12dcc652c71000f16c4824d6f7d" dependencies: - errlop "^1.0.2" - semver "^5.5.0" + errlop "^1.1.1" + semver "^5.6.0" ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" electron-to-chromium@^1.3.47: - version "1.3.78" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.78.tgz#ecb72b5b166ba6598efb384461d63cad74678ebf" + version "1.3.96" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.96.tgz#25770ec99b8b07706dedf3a5f43fa50cb54c4f9a" elliptic@6.3.3: version "6.3.3" @@ -2068,15 +1904,6 @@ elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emojis-list@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -2093,26 +1920,17 @@ end-of-stream@^1.0.0: dependencies: once "^1.4.0" -enhanced-resolve@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - object-assign "^4.0.1" - tapable "^0.2.7" - eol@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/eol/-/eol-0.9.1.tgz#f701912f504074be35c6117a5c4ade49cd547acd" -errlop@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.0.3.tgz#dba29c90cf832c3d2ce469fe515d7e5eef2c6676" +errlop@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.1.1.tgz#d9ae4c76c3e64956c5d79e6e035d6343bfd62250" dependencies: - editions "^1.3.4" + editions "^2.1.2" -errno@^0.1.3, errno@~0.1.1: +errno@~0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" dependencies: @@ -2125,16 +1943,17 @@ 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" + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" 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" dependencies: @@ -2150,7 +1969,7 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@ es6-symbol "~3.1.1" next-tick "1" -es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3: +es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" dependencies: @@ -2158,35 +1977,14 @@ es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0 es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-map@^0.1.3: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-set "~0.1.5" - es6-symbol "~3.1.1" - event-emitter "~0.3.5" - -es6-set@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" - dependencies: - d "1" - es5-ext "~0.10.14" - es6-iterator "~2.0.1" - es6-symbol "3.1.1" - event-emitter "~0.3.5" - -es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: +es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" dependencies: d "1" es5-ext "~0.10.14" -es6-weak-map@^2.0.1, es6-weak-map@^2.0.2: +es6-weak-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" dependencies: @@ -2214,19 +2012,9 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" -escope@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" - dependencies: - es6-map "^0.1.3" - es6-weak-map "^2.0.1" - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-config-standard@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-12.0.0.tgz#638b4c65db0bd5a41319f96bba1f15ddad2107d9" - integrity sha512-COUz8FnXhqFitYj4DTqHzidjIL/t4mumGZto5c7DrBpvWoie+Sn3P4sLEzUGeYhRElWuFEf8K1S1EfvD1vixCQ== eslint-import-resolver-node@^0.3.1: version "0.3.2" @@ -2243,14 +2031,13 @@ eslint-module-utils@^2.2.0: pkg-dir "^1.0.0" eslint-plugin-es@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.3.1.tgz#5acb2565db4434803d1d46a9b4cbc94b345bd028" - integrity sha512-9XcVyZiQRVeFjqHw8qHNDAZcQLqaHlOGGpeYqzYh8S4JYCWTCO3yzyen8yVmA5PratfzTRWDwCOFphtDEG+w/w== + version "1.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.0.tgz#475f65bb20c993fc10e8c8fe77d1d60068072da6" dependencies: eslint-utils "^1.3.0" - regexpp "^2.0.0" + regexpp "^2.0.1" -eslint-plugin-import@^2.10.0: +eslint-plugin-import@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" dependencies: @@ -2266,9 +2053,8 @@ eslint-plugin-import@^2.10.0: resolve "^1.6.0" eslint-plugin-node@^8.0.0: - version "8.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.1.tgz#55ae3560022863d141fa7a11799532340a685964" - integrity sha512-ZjOjbjEi6jd82rIpFSgagv4CHWzG9xsQAVp1ZPlhRnnYxcTgENUVBvhYmkQ7GvT1QFijUSo69RaiOJKhMu6i8w== + version "8.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-8.0.0.tgz#fb9e8911f4543514f154bb6a5924b599aa645568" dependencies: eslint-plugin-es "^1.3.1" eslint-utils "^1.3.1" @@ -2280,17 +2066,14 @@ eslint-plugin-node@^8.0.0: eslint-plugin-promise@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2" - integrity sha512-Si16O0+Hqz1gDHsys6RtFRrW7cCTB6P7p3OJmKp3Y3dxpQE2qwOA7d3xnV+0mBmrPoi0RBnxlCKvqu70te6wjg== eslint-plugin-standard@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.0.tgz#f845b45109c99cd90e77796940a344546c8f6b5c" - integrity sha512-OwxJkR6TQiYMmt1EsNRMe5qG3GsbjlcOhbGUBY4LtavF9DsLaTcoR+j2Tdjqi23oUwKNUqX7qcn5fPStafMdlA== -eslint-scope@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.2.tgz#5f10cd6cabb1965bf479fa65745673439e21cb0e" - integrity sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg== +eslint-scope@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -2298,16 +2081,14 @@ eslint-scope@^4.0.2: eslint-utils@^1.3.0, eslint-utils@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" - integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" -eslint@^5.8.0: - version "5.15.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.1.tgz#8266b089fd5391e0009a047050795b1d73664524" - integrity sha512-NTcm6vQ+PTgN3UBsALw5BMhgO6i5EpIjQF/Xb5tIh3sk9QhrFafujUOczGz4J24JBlzWclSB9Vmx8d+9Z6bFCg== +eslint@^5.10.0: + version "5.11.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.11.1.tgz#8deda83db9f354bf9d3f53f9677af7e0e13eadda" dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.9.1" @@ -2318,7 +2099,7 @@ eslint@^5.8.0: eslint-scope "^4.0.2" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" - espree "^5.0.1" + espree "^5.0.0" esquery "^1.0.1" esutils "^2.0.2" file-entry-cache "^5.0.1" @@ -2328,7 +2109,7 @@ eslint@^5.8.0: ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.2.2" + inquirer "^6.1.0" js-yaml "^3.12.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" @@ -2346,10 +2127,9 @@ eslint@^5.8.0: table "^5.2.3" text-table "^0.2.0" -espree@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" - integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== +espree@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz#fc7f984b62b36a0f543b13fb9cd7b9f4a7f5b65c" dependencies: acorn "^6.0.7" acorn-jsx "^5.0.0" @@ -2514,7 +2294,7 @@ ethereumjs-account@^2.0.3: rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-block@^1.2.2, ethereumjs-block@~1.7.0: +ethereumjs-block@^1.2.2: version "1.7.1" resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz#78b88e6cc56de29a6b4884ee75379b6860333c3f" dependencies: @@ -2524,9 +2304,19 @@ ethereumjs-block@^1.2.2, ethereumjs-block@~1.7.0: ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" -ethereumjs-common@~0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-0.4.1.tgz#27690a24a817b058cc3a2aedef9392e8d7d63984" +ethereumjs-block@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.1.0.tgz#71d1b19e18061f14cf6371bf34ba31a359931360" + dependencies: + async "^2.0.1" + ethereumjs-common "^0.6.0" + ethereumjs-tx "^1.2.2" + ethereumjs-util "^5.0.0" + merkle-patricia-tree "^2.1.2" + +ethereumjs-common@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-0.6.1.tgz#ec98edf315a7f107afb6acc48e937a8266979fae" ethereumjs-testrpc-sc@6.1.6: version "6.1.6" @@ -2534,12 +2324,6 @@ ethereumjs-testrpc-sc@6.1.6: dependencies: source-map-support "^0.5.3" -ethereumjs-testrpc@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/ethereumjs-testrpc/-/ethereumjs-testrpc-6.0.3.tgz#7a0b87bf3670f92f607f98fa6a78801d9741b124" - dependencies: - webpack "^3.0.0" - ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.1, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.4: version "1.3.7" resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" @@ -2569,16 +2353,28 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum safe-buffer "^5.1.1" secp256k1 "^3.0.1" +ethereumjs-util@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.0.0.tgz#f14841c182b918615afefd744207c7932c8536c0" + dependencies: + bn.js "^4.11.0" + create-hash "^1.1.2" + ethjs-util "^0.1.6" + keccak "^1.0.2" + rlp "^2.0.0" + safe-buffer "^5.1.1" + secp256k1 "^3.0.1" + ethereumjs-vm@^2.0.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.4.0.tgz#244f1e35f2755e537a13546111d1a4c159d34b13" + version "2.5.0" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.5.0.tgz#71dde54a093bd813c9defdc6d45ceb8fcca2f603" dependencies: async "^2.1.2" async-eventemitter "^0.2.2" ethereumjs-account "^2.0.3" - ethereumjs-block "~1.7.0" - ethereumjs-common "~0.4.0" - ethereumjs-util "^5.2.0" + ethereumjs-block "~2.1.0" + ethereumjs-common "^0.6.0" + ethereumjs-util "^6.0.0" fake-merkle-patricia-tree "^1.0.1" functional-red-black-tree "^1.0.1" merkle-patricia-tree "^2.1.2" @@ -2586,22 +2382,22 @@ ethereumjs-vm@^2.0.2: safe-buffer "^5.1.1" ethereumjs-wallet@^0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda" + version "0.6.3" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.3.tgz#b0eae6f327637c2aeb9ccb9047b982ac542e6ab1" dependencies: aes-js "^3.1.1" bs58check "^2.1.2" - ethereumjs-util "^5.2.0" - hdkey "^1.0.0" + ethereumjs-util "^6.0.0" + hdkey "^1.1.0" + randombytes "^2.0.6" safe-buffer "^5.1.2" - scrypt.js "^0.2.0" + scrypt.js "^0.3.0" utf8 "^3.0.0" uuid "^3.3.2" -ethers@^4.0.7: - version "4.0.26" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.26.tgz#a4b17184a0ed3db9c88d1b6d28beaa7d0e0ba3e4" - integrity sha512-3hK4S8eAGhuWZ/feip5z17MswjGgjb4lEPJqWO/O0dNqToYLSHhvu6gGQPs8d9f+XfpEB2EYexfF0qjhWiZjUA== +ethers@^4.0.20: + version "4.0.20" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.20.tgz#2b072b283bb19f4870daf42cf5593e5375697504" dependencies: "@types/node" "^10.3.2" aes-js "3.0.0" @@ -2614,14 +2410,6 @@ ethers@^4.0.7: uuid "2.0.1" xmlhttprequest "1.8.0" -ethjs-abi@0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.1.8.tgz#cd288583ed628cdfadaf8adefa3ba1dbcbca6c18" - dependencies: - bn.js "4.11.6" - js-sha3 "0.5.5" - number-to-bn "1.7.0" - ethjs-unit@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" @@ -2629,14 +2417,14 @@ ethjs-unit@0.1.6: bn.js "4.11.6" number-to-bn "1.7.0" -ethjs-util@^0.1.3: +ethjs-util@^0.1.3, ethjs-util@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" dependencies: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -event-emitter@^0.3.5, event-emitter@~0.3.5: +event-emitter@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" dependencies: @@ -2647,10 +2435,6 @@ eventemitter3@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.1.1.tgz#47786bdaa087caf7b1b75e73abc5c7d540158cd0" -events@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" - events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -2750,28 +2534,15 @@ extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" -extended@0.0.6, extended@~0.0.3: - version "0.0.6" - resolved "https://registry.yarnpkg.com/extended/-/extended-0.0.6.tgz#7fb8bf7b9dae397586e48570acfd642c78e50669" - dependencies: - extender "~0.0.5" - -extender@~0.0.5: - version "0.0.10" - resolved "https://registry.yarnpkg.com/extender/-/extender-0.0.10.tgz#589c07482be61a1460b6d81f9c24aa67e8f324cd" - dependencies: - declare.js "~0.0.4" - extendr@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/extendr/-/extendr-2.1.0.tgz#301aa0bbea565f4d2dc8f570f2a22611a8527b56" dependencies: typechecker "~2.0.1" -external-editor@^3.0.3: +external-editor@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" - integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== dependencies: chardet "^0.7.0" iconv-lite "^0.4.24" @@ -2820,15 +2591,6 @@ fake-merkle-patricia-tree@^1.0.1: dependencies: checkpoint-store "^1.1.0" -fast-csv@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-2.4.1.tgz#bd7dd268391f729367b59445b8dd0ad026881b26" - dependencies: - extended "0.0.6" - is-extended "0.0.10" - object-extended "0.0.7" - string-extended "0.0.8" - fast-deep-equal@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" @@ -2882,6 +2644,10 @@ file-type@^6.1.0: version "6.2.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" @@ -2930,19 +2696,14 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^1.2.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" - integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + circular-json "^0.3.1" + graceful-fs "^4.1.2" + rimraf "~2.6.2" + write "^0.2.1" for-each@^0.3.2, for-each@~0.3.3: version "0.3.3" @@ -2965,8 +2726,8 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" form-data@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" dependencies: asynckit "^0.4.0" combined-stream "1.0.6" @@ -3007,17 +2768,9 @@ fs-extra@^2.0.0, fs-extra@^2.1.2: graceful-fs "^4.1.2" jsonfile "^2.1.0" -fs-extra@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" dependencies: graceful-fs "^4.1.2" jsonfile "^4.0.0" @@ -3045,9 +2798,8 @@ fs.realpath@^1.0.0: fs@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.2.tgz#e1f244ef3933c1b2a64bd4799136060d0f5914f8" - integrity sha1-4fJE7zkzwbKmS9R5kTYGDQ9ZFPg= -fsevents@^1.0.0, fsevents@^1.2.2: +fsevents@^1.0.0: version "1.2.4" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: @@ -3071,11 +2823,13 @@ functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" -ganache-cli@^6.1.8: - version "6.1.8" - resolved "https://registry.yarnpkg.com/ganache-cli/-/ganache-cli-6.1.8.tgz#49a8a331683a9652183f82ef1378d17e1814fcd3" +ganache-cli@^6.2.5: + version "6.2.5" + resolved "https://registry.yarnpkg.com/ganache-cli/-/ganache-cli-6.2.5.tgz#efda5115fa3a0c62d7f5729fdd78da70ca55b1ad" dependencies: - source-map-support "^0.5.3" + bn.js "4.11.8" + source-map-support "0.5.9" + yargs "11.1.0" gauge@~2.7.3: version "2.7.4" @@ -3128,13 +2882,6 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - glob@7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -3156,7 +2903,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.3, 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" dependencies: @@ -3185,8 +2932,8 @@ global@~4.3.0: process "~0.5.1" globals@^11.7.0: - version "11.8.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.8.0.tgz#c1ef45ee9bed6badf0663c5cb90e8d1adec1321d" + version "11.9.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.9.0.tgz#bde236808e987f290768a93d065060d78e6ab249" globals@^9.18.0: version "9.18.0" @@ -3212,8 +2959,8 @@ got@7.1.0, got@^7.1.0: url-to-options "^1.0.1" graceful-fs@*, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" "graceful-readlink@>= 1.0.0": version "1.0.1" @@ -3242,10 +2989,10 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" har-validator@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" dependencies: - ajv "^5.3.0" + ajv "^6.5.5" har-schema "^2.0.0" has-ansi@^2.0.0: @@ -3311,7 +3058,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" dependencies: @@ -3327,19 +3074,18 @@ hash-base@^3.0.0: hash.js@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - integrity sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA== dependencies: inherits "^2.0.3" minimalistic-assert "^1.0.0" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" dependencies: inherits "^2.0.3" minimalistic-assert "^1.0.1" -hdkey@^1.0.0: +hdkey@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-1.1.0.tgz#e74e7b01d2c47f797fa65d1d839adb7a44639f29" dependencies: @@ -3391,10 +3137,6 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - i18n@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/i18n/-/i18n-0.8.3.tgz#2d8cf1c24722602c2041d01ba6ae5eaa51388f0e" @@ -3406,6 +3148,10 @@ i18n@^0.8.3: mustache "*" sprintf-js ">=1.0.3" +i@0.3.x: + version "0.3.6" + resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d" + iconv-lite@0.4.23: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" @@ -3431,12 +3177,10 @@ ignore-walk@^3.0.1: ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.0.2: - version "5.0.5" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9" - integrity sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA== + version "5.0.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.4.tgz#33168af4a21e99b00c5d41cbadb6a6cb49903a45" ignorefs@^1.0.0: version "1.2.0" @@ -3465,10 +3209,6 @@ imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -3480,7 +3220,7 @@ inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, i version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -inherits@2.0.1, inherits@=2.0.1: +inherits@=2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" @@ -3488,10 +3228,9 @@ ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inquirer@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" - integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== +inquirer@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -3508,8 +3247,8 @@ inquirer@^6.2.2: through "^2.3.6" interpret@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" invariant@^2.2.2: version "2.2.4" @@ -3613,20 +3352,10 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extended@0.0.10, is-extended@~0.0.3, is-extended@~0.0.8: - version "0.0.10" - resolved "https://registry.yarnpkg.com/is-extended/-/is-extended-0.0.10.tgz#244e140df75bb1c9a3106f412ff182fb534a6d62" - dependencies: - extended "~0.0.3" - is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - is-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" @@ -3657,18 +3386,6 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - dependencies: - is-extglob "^2.1.1" - is-hex-prefixed@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" @@ -3699,10 +3416,6 @@ is-number@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - is-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" @@ -3773,6 +3486,10 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" +iso-url@~0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-0.4.4.tgz#473a45569b6015da0c23f831010aeff69e3841e8" + isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -3820,17 +3537,13 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" -js-sha3@0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.5.tgz#baf0c0e8c54ad5903447df96ade7a4a1bca79a4a" - js-sha3@0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" -js-sha3@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" +js-sha3@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" js-string-escape@^1.0.1: version "1.0.1" @@ -3863,10 +3576,6 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" -json-loader@^0.5.4: - version "0.5.7" - resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" - json-rpc-engine@^3.6.0: version "3.8.0" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz#9d4ff447241792e1d0a232f6ef927302bb0c62a9" @@ -3914,13 +3623,13 @@ json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -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" dependencies: delimit-stream "0.1.0" -json5@^0.5.0, json5@^0.5.1: +json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -3959,11 +3668,11 @@ keccak@^1.0.2: safe-buffer "^5.1.0" keccakjs@^0.2.0, keccakjs@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/keccakjs/-/keccakjs-0.2.1.tgz#1d633af907ef305bbf9f2fa616d56c44561dfa4d" + version "0.2.3" + resolved "https://registry.yarnpkg.com/keccakjs/-/keccakjs-0.2.3.tgz#5e4e969ce39689a3861f445d7752ee3477f9fe72" dependencies: - browserify-sha3 "^0.0.1" - sha3 "^1.1.0" + browserify-sha3 "^0.0.4" + sha3 "^1.2.2" kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" @@ -3991,10 +3700,6 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -4075,18 +3780,6 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -loader-runner@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" - -loader-utils@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" - dependencies: - big.js "^3.1.3" - emojis-list "^2.0.0" - json5 "^0.5.0" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -4098,11 +3791,7 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - -lodash@4.x, lodash@^4.13.1, lodash@^4.14.2, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: +lodash@4.x, lodash@=4.17.11, lodash@^4.13.1, lodash@^4.14.2, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" @@ -4118,10 +3807,6 @@ long-timeout@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/long-timeout/-/long-timeout-0.1.1.tgz#9721d788b47e0bcb5a24c2e2bee1a0da55dab514" -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -4133,8 +3818,8 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" lru-cache@^4.0.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" @@ -4192,7 +3877,6 @@ md5.js@^1.3.4: md5@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= dependencies: charenc "~0.0.1" crypt "~0.0.1" @@ -4232,13 +3916,6 @@ memoizee@^0.4.14: next-tick "1" timers-ext "^0.1.5" -memory-fs@^0.4.0, memory-fs@~0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" - dependencies: - errno "^0.1.3" - readable-stream "^2.0.1" - memorystream@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" @@ -4292,7 +3969,7 @@ micromatch@^2.1.5: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@^3.1.10: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: @@ -4320,11 +3997,10 @@ miller-rabin@^4.0.0: mime-db@~1.37.0: version "1.37.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.20" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" dependencies: mime-db "~1.36.0" @@ -4372,16 +4048,16 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" +minizlib@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" dependencies: minipass "^2.2.1" @@ -4398,7 +4074,7 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -4407,7 +4083,6 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0 mocha-junit-reporter@^1.18.0: version "1.18.0" resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.18.0.tgz#9209a3fba30025ae3ae5e6bfe7f9c5bc3c2e8ee2" - integrity sha512-y3XuqKa2+HRYtg0wYyhW/XsLm2Ps+pqf9HaTAt7+MVUAKFJaNAHOrNseTZo9KCxjfIbxUWwckP5qCDDPUmjSWA== dependencies: debug "^2.2.0" md5 "^2.1.0" @@ -4450,15 +4125,15 @@ mock-fs@^4.1.0: version "4.7.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.7.0.tgz#9f17e219cacb8094f4010e0a8c38589e2b33c299" -moment-timezone@^0.5.0: - version "0.5.21" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.21.tgz#3cba247d84492174dbf71de2a9848fa13207b845" +moment-timezone@^0.5.23: + version "0.5.23" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" dependencies: moment ">= 2.9.0" "moment@>= 2.9.0", moment@^2.22.2: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + version "2.23.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.23.0.tgz#759ea491ac97d54bac5ad776996e2a58cc1bc225" mout@^0.11.0: version "0.11.1" @@ -4480,8 +4155,8 @@ multihashes@^0.4.5: varint "^5.0.0" mustache@*: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-3.0.0.tgz#3de22dd9ba38152f7355399a953dd4528c403338" + version "3.0.1" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-3.0.1.tgz#873855f23aa8a95b150fb96d9836edbc5a1d248a" mustache@^2.3.0: version "2.3.2" @@ -4491,6 +4166,10 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" +mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + mz@^2.6.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -4503,9 +4182,9 @@ nan@2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" -nan@^2.0.8, nan@^2.2.1, nan@^2.3.3, nan@^2.9.2: - version "2.11.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" +nan@^2.0.8, nan@^2.11.0, nan@^2.2.1, nan@^2.3.3, nan@^2.9.2: + version "2.12.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" nano-json-stream-parser@^0.1.2: version "0.1.2" @@ -4531,6 +4210,10 @@ natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" +ncp@1.0.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-1.0.1.tgz#d15367e5cb87432ba117d2bf80fdf45aecfb4246" + needle@^2.2.1: version "2.2.4" resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" @@ -4543,10 +4226,6 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" -neo-async@^2.5.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc" - next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -4554,7 +4233,6 @@ next-tick@1: nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-async-loop@^1.2.2: version "1.2.2" @@ -4574,34 +4252,6 @@ node-fetch@^1.0.1, node-fetch@~1.7.1: encoding "^0.1.11" is-stream "^1.0.1" -node-libs-browser@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^1.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.0" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.10.3" - vm-browserify "0.0.4" - node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" @@ -4618,10 +4268,10 @@ node-pre-gyp@^0.10.0: tar "^4" node-schedule@^1.2.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.0.tgz#e7a7e816a7f2550d5b170bd106e765db28bdf030" + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-schedule/-/node-schedule-1.3.1.tgz#6909dd644211bca153b15afc62e1dc0afa7d28be" dependencies: - cron-parser "^2.4.0" + cron-parser "^2.7.3" long-timeout "0.1.1" sorted-array-functions "^1.0.0" @@ -4647,7 +4297,7 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -4706,14 +4356,6 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-extended@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/object-extended/-/object-extended-0.0.7.tgz#84fd23f56b15582aeb3e88b05cb55d2432d68a33" - dependencies: - array-extended "~0.0.4" - extended "~0.0.3" - is-extended "~0.0.3" - object-inspect@~1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" @@ -4795,10 +4437,6 @@ original-require@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/original-require/-/original-require-1.0.1.tgz#0f130471584cd33511c5ec38c8d59213f9ac5e20" -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -4858,17 +4496,6 @@ p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - -parent-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" - integrity sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA== - dependencies: - callsites "^3.0.0" - parse-asn1@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" @@ -4909,14 +4536,6 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" -path-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -4939,7 +4558,7 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" -path-parse@^1.0.5: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -5007,6 +4626,18 @@ pkg-dir@^1.0.0: dependencies: find-up "^1.0.0" +pkginfo@0.3.x: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" + +pkginfo@0.x.x: + version "0.4.1" + resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -5027,9 +4658,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@^1.14.3: - version "1.14.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" +prettier@^1.15.3: + version "1.15.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a" private@^0.1.6, private@^0.1.8: version "0.1.8" @@ -5039,17 +4670,13 @@ process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - process@~0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" progress@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" promise-to-callback@^1.0.0: version "1.0.0" @@ -5058,6 +4685,17 @@ promise-to-callback@^1.0.0: is-fn "^1.0.0" set-immediate-shim "^1.0.1" +prompt@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prompt/-/prompt-1.0.0.tgz#8e57123c396ab988897fb327fd3aedc3e735e4fe" + dependencies: + colors "^1.1.2" + pkginfo "0.x.x" + read "1.0.x" + revalidator "0.1.x" + utile "0.3.x" + winston "2.1.x" + prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" @@ -5080,9 +4718,9 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" -psl@^1.1.24: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" +psl@^1.1.24, psl@^1.1.28: + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" public-encrypt@^4.0.0: version "4.0.3" @@ -5095,15 +4733,11 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -5119,23 +4753,15 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - randomatic@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" + version "3.1.1" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" dependencies: is-number "^4.0.0" kind-of "^6.0.0" math-random "^1.0.1" -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" dependencies: @@ -5175,22 +4801,22 @@ rc@^1.2.7: strip-json-comments "~2.0.1" react-dom@^16.2.0: - version "16.5.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7" + version "16.7.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.7.0.tgz#a17b2a7ca89ee7390bc1ed5eb81783c7461748b8" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - schedule "^0.5.0" + scheduler "^0.12.0" react@^16.2.0: - version "16.5.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.5.2.tgz#19f6b444ed139baa45609eee6dc3d318b3895d42" + version "16.7.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - schedule "^0.5.0" + scheduler "^0.12.0" read-pkg-up@^1.0.1: version "1.0.1" @@ -5222,6 +4848,12 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" +read@1.0.x: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + dependencies: + mute-stream "~0.0.4" + readable-stream@^1.0.33: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -5231,7 +4863,7 @@ readable-stream@^1.0.33: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -5260,10 +4892,6 @@ readdirp@^2.0.0: micromatch "^3.1.10" readable-stream "^2.0.2" -readline-sync@^1.4.9: - version "1.4.9" - resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.9.tgz#3eda8e65f23cd2a17e61301b1f0003396af5ecda" - rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -5303,10 +4931,9 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpp@^2.0.0, regexpp@^2.0.1: +regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== regexpu-core@^2.0.0: version "2.0.0" @@ -5426,10 +5053,10 @@ resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolve@^1.1.6, resolve@^1.5.0, resolve@^1.6.0, resolve@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + version "1.9.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.9.0.tgz#a14c6fdfa8f92a7df1d996cb7105fa744658ea06" dependencies: - path-parse "^1.0.5" + path-parse "^1.0.6" resolve@~1.7.1: version "1.7.1" @@ -5454,17 +5081,15 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - dependencies: - align-text "^0.1.1" +revalidator@0.1.x: + version "0.1.8" + resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" -rimraf@2, rimraf@^2.2.8, rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" +rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.6.1, rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" dependencies: - glob "^7.0.5" + glob "^7.1.3" rimraf@2.6.3: version "2.6.3" @@ -5481,8 +5106,8 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rlp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.1.0.tgz#e4f9886d5a982174f314543831e36e1a658460f9" + version "2.2.1" + resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.1.tgz#9cacf53ad2579163cc56fba64b1f4336f1f2fa46" dependencies: safe-buffer "^5.1.1" @@ -5496,10 +5121,9 @@ rustbn.js@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/rustbn.js/-/rustbn.js-0.2.0.tgz#8082cb886e707155fd1cb6f23bd591ab8d55d0ca" -rxjs@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== +rxjs@^6.1.0: + version "6.3.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" dependencies: tslib "^1.9.0" @@ -5545,10 +5169,9 @@ scandirectory@^2.5.0: safefs "^3.1.2" taskgroup "^4.0.5" -scheduler@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.10.0.tgz#7988de90fe7edccc774ea175a783e69c40c521e1" - integrity sha512-+TSTVTCBAA3h8Anei3haDc1IRwMeDmtI/y/o3iBe3Mjl2vwYF9DtPDt929HyRmV/e7au7CLu8sc4C4W0VOs29w== +scheduler@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b" dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -5560,15 +5183,22 @@ scrypt-async@^1.2.0: scrypt-js@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" - integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== -scrypt.js@0.2.0, scrypt.js@^0.2.0: +scrypt.js@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada" dependencies: scrypt "^6.0.2" scryptsy "^1.2.1" +scrypt.js@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.3.0.tgz#6c62d61728ad533c8c376a2e5e3e86d41a95c4c0" + dependencies: + scryptsy "^1.2.1" + optionalDependencies: + scrypt "^6.0.2" + scrypt@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/scrypt/-/scrypt-6.0.3.tgz#04e014a5682b53fa50c2d5cce167d719c06d870d" @@ -5582,8 +5212,8 @@ 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" + version "3.6.1" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.6.1.tgz#f0475d42096218ff00e45a127242abdff9285335" dependencies: bindings "^1.2.1" bip66 "^1.1.3" @@ -5604,7 +5234,7 @@ semaphore@>=1.0.1, semaphore@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -5679,7 +5309,7 @@ setimmediate@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -5694,7 +5324,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sha3@^1.1.0: +sha3@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.2.tgz#a66c5098de4c25bc88336ec8b4817d005bca7ba9" dependencies: @@ -5718,9 +5348,9 @@ shelljs@^0.7.4: interpret "^1.0.0" rechoir "^0.6.2" -shelljs@^0.8.1, shelljs@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.2.tgz#345b7df7763f4c2340d584abb532c5f752ca9e35" +shelljs@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -5746,10 +5376,9 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +slice-ansi@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" @@ -5794,15 +5423,15 @@ sol-explore@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/sol-explore/-/sol-explore-1.6.2.tgz#43ae8c419fd3ac056a05f8a9d1fb1022cd41ecc2" -sol-merger@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/sol-merger/-/sol-merger-0.1.2.tgz#1f12500f42d427dc0ec8e4c113392acd8a6f62d9" +sol-merger@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/sol-merger/-/sol-merger-0.1.3.tgz#184284ba4811aebe8950f510df4e8218f568b35f" dependencies: - bluebird "^3.5.0" - cli-color "^1.2.0" - commander "^2.11.0" - debug "^3.0.1" - fs-extra "^4.0.2" + bluebird "^3.5.3" + cli-color "^1.4.0" + commander "^2.19.0" + debug "^3.2.6" + fs-extra "^7.0.1" glob "^7.1.2" solc@0.4.24: @@ -5815,7 +5444,7 @@ solc@0.4.24: semver "^5.3.0" yargs "^4.7.1" -solc@^0.4.19, solc@^0.4.2, solc@^0.4.24: +solc@^0.4.2: version "0.4.25" resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.25.tgz#06b8321f7112d95b4b903639b1138a4d292f5faa" dependencies: @@ -5865,26 +5494,27 @@ solium-plugin-security@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/solium-plugin-security/-/solium-plugin-security-0.1.1.tgz#2a87bcf8f8c3abf7d198e292e4ac080284e3f3f6" -solium@^1.1.6: - version "1.1.8" - resolved "https://registry.yarnpkg.com/solium/-/solium-1.1.8.tgz#35d30a15c572a233ce8a90226d6cfccb762fadb7" +solium@^1.1.8: + version "1.2.1" + resolved "https://registry.yarnpkg.com/solium/-/solium-1.2.1.tgz#ead11f2921ba2337461752155cc96149ea08f247" 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" +solparse@2.2.7: + version "2.2.7" + resolved "https://registry.yarnpkg.com/solparse/-/solparse-2.2.7.tgz#5479ff4ba4ca6900df89b902b5af1ee23b816250" dependencies: mocha "^4.0.1" pegjs "^0.10.0" @@ -5894,10 +5524,6 @@ sorted-array-functions@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz#43265b21d6e985b7df31621b1c11cc68d8efc7c3" -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - source-map-resolve@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" @@ -5908,24 +5534,24 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - dependencies: - source-map "^0.5.6" - -source-map-support@^0.5.3: +source-map-support@0.5.9, source-map-support@^0.5.3: version "0.5.9" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" dependencies: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: +source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -5940,8 +5566,8 @@ source-map@~0.2.0: amdefine ">=0.0.4" spdx-correct@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.2.tgz#19bb409e91b47b1ad54159243f7312a858db3c2e" + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" @@ -5958,8 +5584,8 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f" + version "3.0.3" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -5968,16 +5594,16 @@ split-string@^3.0.1, split-string@^3.0.2: extend-shallow "^3.0.0" sprintf-js@>=1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.15.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.1.tgz#b79a089a732e346c6e0714830f36285cd38191a2" + version "1.16.0" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -6016,36 +5642,10 @@ stealthy-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" -stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" -string-extended@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/string-extended/-/string-extended-0.0.8.tgz#741957dff487b0272a79eec5a44f239ee6f17ccd" - dependencies: - array-extended "~0.0.5" - date-extended "~0.0.3" - extended "~0.0.3" - is-extended "~0.0.3" - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -6078,16 +5678,16 @@ string.prototype.trim@~1.1.2: es-abstract "^1.5.0" function-bind "^1.0.2" -string_decoder@^1.0.0, string_decoder@~1.1.1: +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: safe-buffer "~5.1.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -6103,7 +5703,6 @@ strip-ansi@^4.0.0: strip-ansi@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" - integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== dependencies: ansi-regex "^4.0.0" @@ -6159,12 +5758,6 @@ supports-color@^3.1.0: dependencies: has-flag "^1.0.0" -supports-color@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" - dependencies: - has-flag "^2.0.0" - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -6189,23 +5782,18 @@ swarm-js@0.1.37: tar.gz "^1.0.5" xhr-request-promise "^0.1.2" -table@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" - integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== +table@^5.0.2: + version "5.1.1" + resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" dependencies: - ajv "^6.9.1" + ajv "^6.6.1" lodash "^4.17.11" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -tapable@^0.2.7: - version "0.2.8" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" + slice-ansi "2.0.0" + 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" + version "4.9.2" + resolved "https://registry.yarnpkg.com/tape/-/tape-4.9.2.tgz#f233e40f09dc7e00fcf9b26755453c3822ad28c0" dependencies: deep-equal "~1.0.1" defined "~1.0.0" @@ -6252,13 +5840,13 @@ tar@^2.1.1: inherits "2" tar@^4: - version "4.4.6" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" dependencies: - chownr "^1.0.1" + chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" + minipass "^2.3.4" + minizlib "^1.1.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" yallist "^3.0.2" @@ -6294,12 +5882,6 @@ timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" -timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" - dependencies: - setimmediate "^1.0.4" - timers-ext@^0.1.5: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" @@ -6323,10 +5905,6 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - to-buffer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" @@ -6357,7 +5935,14 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -tough-cookie@>=2.3.3, tough-cookie@~2.4.3: +tough-cookie@>=2.3.3: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" dependencies: @@ -6365,8 +5950,8 @@ tough-cookie@>=2.3.3, tough-cookie@~2.4.3: punycode "^1.4.1" tree-kill@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" + version "1.2.1" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.1.tgz#5398f374e2f292b9dcc7b2e71e30a5c3bb6c743a" trim-right@^1.0.1: version "1.0.1" @@ -6376,42 +5961,23 @@ trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" -truffle-blockchain-utils@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/truffle-blockchain-utils/-/truffle-blockchain-utils-0.0.5.tgz#a4e5c064dadd69f782a137f3d276d21095da7a47" - -truffle-contract-schema@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/truffle-contract-schema/-/truffle-contract-schema-2.0.1.tgz#9bf821d32e26e674ba15eb5d40f96b10b1c9d568" - dependencies: - ajv "^5.1.1" - crypto-js "^3.1.9-1" - debug "^3.1.0" - -truffle-contract@^3.0.4: - version "3.0.6" - resolved "https://registry.yarnpkg.com/truffle-contract/-/truffle-contract-3.0.6.tgz#2ef6fc32d7faafa9f4aed8e50001a9fdea342192" - dependencies: - ethjs-abi "0.1.8" - truffle-blockchain-utils "^0.0.5" - truffle-contract-schema "^2.0.1" - truffle-error "^0.0.3" - web3 "0.20.6" - -truffle-error@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/truffle-error/-/truffle-error-0.0.3.tgz#4bf55242e14deee1c7194932709182deff2c97ca" - -truffle-hdwallet-provider-privkey@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider-privkey/-/truffle-hdwallet-provider-privkey-0.2.0.tgz#91e9e8a6a5005970a5b442fa89fc198ecd1f71ef" - integrity sha512-p4dCmB/roQaHaRMe7Ihej4/Cdmq7Usi6aZsPv/cc2x7S5bYLSwwpgQBdjz4PjPSgNh8zqLte6ZhWkkW1CEq1iQ== +truffle-hdwallet-provider-privkey@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider-privkey/-/truffle-hdwallet-provider-privkey-0.3.0.tgz#6688f5f3db5dce2cb9f502f7b52dab7b5e2522a3" dependencies: ethereumjs-tx "^1.3.4" ethereumjs-wallet "^0.6.0" web3 "^0.20.6" web3-provider-engine "^13.8.0" +truffle-hdwallet-provider@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/truffle-hdwallet-provider/-/truffle-hdwallet-provider-1.0.3.tgz#4ae845f9b2de23f47c4f0fbc6ef2d4b13b494604" + dependencies: + any-promise "^1.3.0" + bindings "^1.3.1" + websocket "^1.0.28" + truffle-wallet-provider@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/truffle-wallet-provider/-/truffle-wallet-provider-0.0.5.tgz#db59ce6fa1c558766011137509a94dfca8d1408e" @@ -6431,11 +5997,6 @@ truffle@4.1.14: tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" tunnel-agent@^0.6.0: version "0.6.0" @@ -6469,30 +6030,21 @@ typechecker@^2.0.8: resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-2.1.0.tgz#d1c2093a54ff8a19f58cff877eeaa54f2242d383" typechecker@^4.3.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-4.6.0.tgz#d245d9c2df21147d5e2a942fff170b68ece73c87" + version "4.7.0" + resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-4.7.0.tgz#5249f427358f45b7250c4924fd4d01ed9ba435e9" dependencies: - editions "^2.0.2" + editions "^2.1.0" typechecker@~2.0.1: version "2.0.8" resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-2.0.8.tgz#e83da84bb64c584ccb345838576c40b0337db82e" -typedarray-to-buffer@^3.1.2: +typedarray-to-buffer@^3.1.2, typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" dependencies: is-typedarray "^1.0.0" -uglify-js@^2.8.29: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" @@ -6500,18 +6052,6 @@ uglify-js@^3.1.4: commander "~2.17.1" source-map "~0.6.1" -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - -uglifyjs-webpack-plugin@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" - dependencies: - source-map "^0.5.6" - uglify-js "^2.8.29" - webpack-sources "^1.0.1" - ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" @@ -6544,7 +6084,7 @@ universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" -unorm@^1.3.3: +unorm@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.4.1.tgz#364200d5f13646ca8bcd44490271335614792300" @@ -6559,10 +6099,6 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.0.5: - version "1.1.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" - uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -6587,13 +6123,6 @@ url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -6614,17 +6143,16 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -util@^0.10.3: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" +utile@0.3.x: + version "0.3.0" + resolved "https://registry.yarnpkg.com/utile/-/utile-0.3.0.tgz#1352c340eb820e4d8ddba039a4fbfaa32ed4ef3a" dependencies: - inherits "2.0.3" + async "~0.9.0" + deep-equal "~0.2.1" + i "0.3.x" + mkdirp "0.x.x" + ncp "1.0.x" + rimraf "2.x.x" utils-merge@1.0.1: version "1.0.1" @@ -6638,10 +6166,6 @@ uuid@^3.0.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" -valid-url@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -6665,20 +6189,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vm-browserify@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" - dependencies: - indexof "0.0.1" - -watchpack@^1.4.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - dependencies: - chokidar "^2.0.2" - graceful-fs "^4.1.2" - neo-async "^2.5.0" - watchr@~2.4.13: version "2.4.13" resolved "https://registry.yarnpkg.com/watchr/-/watchr-2.4.13.tgz#d74847bb4d6f90f61fe2c74f9f68662aa0e07601" @@ -6938,16 +6448,6 @@ web3@0.20.2: xhr2 "*" xmlhttprequest "*" -web3@0.20.6: - version "0.20.6" - resolved "https://registry.yarnpkg.com/web3/-/web3-0.20.6.tgz#3e97306ae024fb24e10a3d75c884302562215120" - dependencies: - bignumber.js "git+https://github.com/frozeman/bignumber.js-nolookahead.git" - crypto-js "^3.1.4" - utf8 "^2.1.1" - xhr2 "*" - xmlhttprequest "*" - web3@1.0.0-beta.34: version "1.0.0-beta.34" resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.34.tgz#347e561b784098cb5563315f490479a1d91f2ab1" @@ -6989,39 +6489,14 @@ web3@^0.20.6: xhr2-cookies "^1.1.0" xmlhttprequest "*" -webpack-sources@^1.0.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack@^3.0.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.12.0.tgz#3f9e34360370602fcf639e97939db486f4ec0d74" +websocket@^1.0.28: + version "1.0.28" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.28.tgz#9e5f6fdc8a3fe01d4422647ef93abdd8d45a78d3" dependencies: - acorn "^5.0.0" - acorn-dynamic-import "^2.0.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - async "^2.1.2" - enhanced-resolve "^3.4.0" - escope "^3.6.0" - interpret "^1.0.0" - json-loader "^0.5.4" - json5 "^0.5.1" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - mkdirp "~0.5.0" - node-libs-browser "^2.0.0" - source-map "^0.5.3" - supports-color "^4.2.1" - tapable "^0.2.7" - uglifyjs-webpack-plugin "^0.4.6" - watchpack "^1.4.0" - webpack-sources "^1.0.1" - yargs "^8.0.2" + debug "^2.2.0" + nan "^2.11.0" + typedarray-to-buffer "^3.1.5" + yaeti "^0.0.6" "websocket@git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible": version "1.0.26" @@ -7056,14 +6531,22 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - window-size@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" +winston@2.1.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/winston/-/winston-2.1.1.tgz#3c9349d196207fd1bdff9d4bc43ef72510e3a12e" + dependencies: + async "~1.0.0" + colors "1.0.x" + cycle "1.0.x" + eyes "0.1.x" + isstream "0.1.x" + pkginfo "0.3.x" + stack-trace "0.0.x" + winston@^2.3.1: version "2.4.4" resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.4.tgz#a01e4d1d0a103cf4eada6fc1f886b3110d71c34b" @@ -7075,10 +6558,6 @@ winston@^2.3.1: isstream "0.1.x" stack-trace "0.0.x" -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - wordwrap@^1.0.0, wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -7153,7 +6632,6 @@ xhr@^2.0.4, xhr@^2.2.0, xhr@^2.3.3: xml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" - integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= xmlhttprequest@*, xmlhttprequest@1.8.0: version "1.8.0" @@ -7186,8 +6664,8 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" yargs-parser@^2.4.1: version "2.4.1" @@ -7196,12 +6674,6 @@ yargs-parser@^2.4.1: camelcase "^3.0.0" lodash.assign "^4.0.6" -yargs-parser@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" - dependencies: - camelcase "^4.1.0" - yargs-parser@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" @@ -7214,9 +6686,9 @@ yargs-parser@^9.0.2: dependencies: camelcase "^4.1.0" -yargs@^10.0.3: - version "10.1.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" +yargs@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -7229,11 +6701,11 @@ yargs@^10.0.3: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^8.1.0" + yargs-parser "^9.0.2" -yargs@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" +yargs@^10.0.3: + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -7246,7 +6718,7 @@ yargs@^11.0.0: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^9.0.2" + yargs-parser "^8.1.0" yargs@^4.6.0, yargs@^4.7.1: version "4.8.1" @@ -7267,33 +6739,6 @@ yargs@^4.6.0, yargs@^4.7.1: y18n "^3.2.1" yargs-parser "^2.4.1" -yargs@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" - dependencies: - camelcase "^4.1.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - read-pkg-up "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^7.0.0" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"