diff --git a/CLI/commands/dividends_manager.js b/CLI/commands/dividends_manager.js index f4aca926e..20880f7c0 100644 --- a/CLI/commands/dividends_manager.js +++ b/CLI/commands/dividends_manager.js @@ -6,6 +6,7 @@ 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`; @@ -146,7 +147,7 @@ async function dividendsManager() { if (currentDividends.length > 0) { options.push('Manage existing dividends'); } - options.push('Create new dividends'); + options.push('Create new dividends', 'Reclaim ETH or ERC20 tokens from contract'); let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' }); let selected = index != -1 ? options[index] : 'RETURN'; @@ -178,6 +179,9 @@ async function dividendsManager() { case 'Create new dividends': await createDividends(); break; + case 'Reclaim ETH or ERC20 tokens from contract': + await reclaimFromContract(); + break; case 'RETURN': return; } @@ -238,9 +242,12 @@ async function manageExistingDividend(dividendIndex) { 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]; @@ -266,12 +273,12 @@ async function manageExistingDividend(dividendIndex) { 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: ${web3.utils.fromWei(dividend.amount)} ${dividendTokenSymbol}`); - console.log(`- Claimed amount: ${web3.utils.fromWei(dividend.claimedAmount)} ${dividendTokenSymbol}`); + 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: ${web3.utils.fromWei(taxesToWithHeld)} ${dividendTokenSymbol}`); - console.log(` Withheld to-date: ${web3.utils.fromWei(dividend.totalWithheld)} ${dividendTokenSymbol}`); - console.log(` Withdrawn to-date: ${web3.utils.fromWei(dividend.totalWithheldWithdrawn)} ${dividendTokenSymbol}`); + 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} `); @@ -300,6 +307,7 @@ async function manageExistingDividend(dividendIndex) { 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 @@ -314,14 +322,17 @@ async function manageExistingDividend(dividendIndex) { await pushDividends(dividendIndex, dividend.checkpointId); break; case 'Explore account': - await exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol); + await exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol, dividendTokenDecimals); break; case 'Withdraw withholding': - await withdrawWithholding(dividendIndex, dividendTokenSymbol); + await withdrawWithholding(dividendIndex, dividendTokenSymbol, dividendTokenDecimals); break; case 'Reclaim expired dividends': - await reclaimedDividend(dividendIndex, dividendTokenSymbol); + await reclaimedDividend(dividendIndex, dividendTokenSymbol, dividendTokenDecimals); return; + case 'Reclaim ETH or ERC20 tokens from contract': + await reclaim + break; case 'RETURN': return; } @@ -376,6 +387,7 @@ 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 { @@ -390,6 +402,7 @@ async function createDividends() { if (erc20Symbol != null) { token = new web3.eth.Contract(abis.erc20(), dividendToken); dividendSymbol = erc20Symbol; + dividendTokenDecimals = await token.methods.decimals().call(); } else { console.log(chalk.red(`${dividendToken} is not a valid ERC20 token address!!`)); } @@ -397,10 +410,10 @@ async function createDividends() { } let dividendAmount = readlineSync.question(`How much ${dividendSymbol} would you like to distribute to token holders? `); - let dividendAmountBN = new web3.utils.BN(dividendAmount); - let issuerBalance = new web3.utils.BN(web3.utils.fromWei(await getBalance(Issuer.address, dividendToken))); + 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} ${dividendSymbol}.You need ${dividendAmountBN.sub(issuerBalance)} ${dividendSymbol} more!`)); + 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); @@ -412,21 +425,21 @@ async function createDividends() { let createDividendAction; if (dividendsType == 'ERC20') { - let approveAction = token.methods.approve(currentDividendsModule._address, web3.utils.toWei(dividendAmountBN)); + 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, web3.utils.toWei(dividendAmountBN), checkpointId, web3.utils.toHex(dividendName)); + 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, web3.utils.toWei(dividendAmountBN), checkpointId, excluded[0], web3.utils.toHex(dividendName)); + createDividendAction = currentDividendsModule.methods.createDividendWithCheckpointAndExclusions(maturityTime, expiryTime, token.options.address, dividendAmountBN, checkpointId, excluded[0], web3.utils.toHex(dividendName)); } } else { if (useDefaultExcluded) { - createDividendAction = currentDividendsModule.methods.createDividend(maturityTime, expiryTime, token.options.address, web3.utils.toWei(dividendAmountBN), web3.utils.toHex(dividendName)); + 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, web3.utils.toWei(dividendAmountBN), excluded[0], web3.utils.toHex(dividendName)); + createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, token.options.address, dividendAmountBN, excluded[0], web3.utils.toHex(dividendName)); } } let receipt = await common.sendTransaction(createDividendAction); @@ -448,7 +461,7 @@ async function createDividends() { createDividendAction = currentDividendsModule.methods.createDividendWithExclusions(maturityTime, expiryTime, excluded, web3.utils.toHex(dividendName)); } } - let receipt = await common.sendTransaction(createDividendAction, { value: web3.utils.toWei(dividendAmountBN) }); + let receipt = await common.sendTransaction(createDividendAction, { value: dividendAmountBN }); let event = common.getEventFromLogs(currentDividendsModule._jsonInterface, receipt.logs, 'EtherDividendDeposited'); console.log(` Dividend ${ event._dividendIndex} deposited` @@ -457,6 +470,33 @@ Dividend ${ event._dividendIndex} deposited` } } +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 + } +} + function showInvestors(investorsArray, claimedArray, excludedArray) { let dataTable = [['Investor', 'Has claimed', 'Is excluded']]; for (let i = 0; i < investorsArray.length; i++) { @@ -470,7 +510,7 @@ function showInvestors(investorsArray, claimedArray, excludedArray) { console.log(table(dataTable)); } -function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investorArray, _claimedArray, _excludedArray, _withheldArray, _amountArray) { +function showReport(_name, _tokenSymbol, _tokenDecimals, _amount, _witthheld, _claimed, _investorArray, _claimedArray, _excludedArray, _withheldArray, _amountArray) { let title = `${_name.toUpperCase()} DIVIDEND REPORT`; let dataTable = [[ @@ -485,10 +525,10 @@ function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investo let investor = _investorArray[i]; let excluded = _excludedArray[i]; let withdrawn = _claimedArray[i] ? 'YES' : 'NO'; - let amount = !excluded ? web3.utils.fromWei(web3.utils.toBN(_amountArray[i]).add(web3.utils.toBN(_withheldArray[i]))) : 0; - let withheld = !excluded ? web3.utils.fromWei(_withheldArray[i]) : 'NA'; + 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 ? web3.utils.fromWei(_amountArray[i]) : 0; + let received = !excluded ? parseFloat(_amountArray[i]) / Math.pow(10, _tokenDecimals) : 0; dataTable.push([ investor, amount, @@ -501,9 +541,9 @@ function showReport(_name, _tokenSymbol, _amount, _witthheld, _claimed, _investo 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: ${web3.utils.fromWei(_amount)} ${_tokenSymbol} `); - console.log(`- Total amount of taxes withheld: ${web3.utils.fromWei(_witthheld)} ${_tokenSymbol} `); - console.log(`- Total amount of dividends distributed: ${web3.utils.fromWei(_claimed)} ${_tokenSymbol} `); + 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)); @@ -536,7 +576,7 @@ async function pushDividends(dividendIndex, checkpointId) { } } -async function exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol) { +async function exploreAccount(dividendIndex, dividendTokenAddress, dividendTokenSymbol, dividendTokenDecimals) { let account = readlineSync.question('Enter address to explore: ', { limit: function (input) { return web3.utils.isAddress(input); @@ -553,33 +593,33 @@ async function exploreAccount(dividendIndex, dividendTokenAddress, dividendToken console.log(); console.log(`Security token balance: ${web3.utils.fromWei(securityTokenBalance)} ${tokenSymbol} `); - console.log(`Dividend token balance: ${web3.utils.fromWei(dividendTokenBalance)} ${dividendTokenSymbol} `); + 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: ${web3.utils.fromWei(dividendBalance)} ${dividendTokenSymbol} `); - console.log(`Tax withheld: ${web3.utils.fromWei(dividendTax)} ${dividendTokenSymbol} `); + console.log(`Dividends available: ${dividendBalance / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} `); + console.log(`Tax withheld: ${dividendTax / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} `); } } console.log(); } -async function withdrawWithholding(dividendIndex, dividendTokenSymbol) { +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 ${web3.utils.fromWei(event._withheldAmount)} ${dividendTokenSymbol} from dividend ${event._dividendIndex} tax withholding.`)); + console.log(chalk.green(`Successfully withdrew ${event._withheldAmount / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} from dividend ${event._dividendIndex} tax withholding.`)); } -async function reclaimedDividend(dividendIndex, dividendTokenSymbol) { +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 ${ web3.utils.fromWei(event._claimedAmount)} ${dividendTokenSymbol} +Reclaimed amount ${event._claimedAmount / Math.pow(10, dividendTokenDecimals)} ${dividendTokenSymbol} to account ${ event._claimer} ` ); } @@ -699,7 +739,7 @@ async function selectDividend(dividends) { let result = null; let options = dividends.map(function (d) { return `${d.name} - Amount: ${web3.utils.fromWei(d.amount)} ${d.tokenSymbol} + 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')} @@ -715,7 +755,7 @@ async function selectDividend(dividends) { } async function getDividends() { - function DividendData(_index, _created, _maturity, _expiry, _amount, _claimedAmount, _name, _tokenSymbol) { + function DividendData(_index, _created, _maturity, _expiry, _amount, _claimedAmount, _name, _tokenSymbol, _tokenDecimals) { this.index = _index; this.created = _created; this.maturity = _maturity; @@ -724,6 +764,7 @@ async function getDividends() { this.claimedAmount = _claimedAmount; this.name = _name; this.tokenSymbol = _tokenSymbol; + this.tokenDecimals = _tokenDecimals; } let dividends = []; @@ -736,9 +777,12 @@ async function getDividends() { 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( @@ -749,7 +793,8 @@ async function getDividends() { amountArray[i], claimedAmountArray[i], web3.utils.hexToUtf8(nameArray[i]), - tokenSymbol + tokenSymbol, + dividendTokenDecimals ) ); } diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index f5d8064a6..2cf7b113d 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -21,7 +21,7 @@ let erc20DividendCheckpointABI; let etherDividendCheckpointABI; let moduleInterfaceABI; let ownableABI; -let iSTOABI; +let stoABI; let iTransferManagerABI; let moduleFactoryABI; let erc20ABI; @@ -50,7 +50,7 @@ try { 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; - iSTOABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/ISTO.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; @@ -129,8 +129,8 @@ module.exports = { ownable: function () { return ownableABI; }, - ISTO: function () { - return iSTOABI; + sto: function () { + return stoABI; }, ITransferManager: function () { return iTransferManagerABI; diff --git a/CLI/commands/sto_manager.js b/CLI/commands/sto_manager.js index 4a96b1088..756787e2d 100644 --- a/CLI/commands/sto_manager.js +++ b/CLI/commands/sto_manager.js @@ -72,7 +72,7 @@ function selectExistingSTO(stoModules, showPaused) { if (!showPaused) { filteredModules = stoModules.filter(m => !m.paused); } - let options = filteredModules.map(m => `${m.name} at ${m.address}`); + 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; @@ -119,27 +119,34 @@ async function modifySTO(selectedSTO, currentSTO) { 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(); - let options = await Promise.all(availableModules.map(async function (m) { + moduleList = 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 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 ? options[index] : '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); + let cappedSTO = await cappedSTO_launch(stoConfig, factorySelected); await cappedSTO_status(cappedSTO); break; case 'USDTieredSTO': - let usdTieredSTO = await usdTieredSTO_launch(stoConfig); + let usdTieredSTO = await usdTieredSTO_launch(stoConfig, factorySelected); await usdTieredSTO_status(usdTieredSTO); break; } @@ -148,12 +155,11 @@ async function addSTOModule(stoConfig) { //////////////// // Capped STO // //////////////// -async function cappedSTO_launch(stoConfig) { +async function cappedSTO_launch(stoConfig, factoryAddress) { console.log(chalk.blue('Launch STO - Capped STO in No. of Tokens')); let cappedSTOFactoryABI = abis.cappedSTOFactory(); - let cappedSTOFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.STO, "CappedSTO"); - let cappedSTOFactory = new web3.eth.Contract(cappedSTOFactoryABI, cappedSTOFactoryAddress); + let cappedSTOFactory = new web3.eth.Contract(cappedSTOFactoryABI, factoryAddress); cappedSTOFactory.setProvider(web3.currentProvider); let stoFee = new web3.utils.BN(await cappedSTOFactory.methods.getSetupCost().call()); @@ -217,7 +223,7 @@ async function cappedSTO_launch(stoConfig) { cappedSTOconfig.wallet] ); - let addModuleAction = securityToken.methods.addModule(cappedSTOFactoryAddress, bytesSTO, stoFee, 0); + 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}`); @@ -508,12 +514,11 @@ function timesConfigUSDTieredSTO(stoConfig) { return times; } -async function usdTieredSTO_launch(stoConfig) { +async function usdTieredSTO_launch(stoConfig, factoryAddress) { console.log(chalk.blue('Launch STO - USD pegged tiered STO')); let usdTieredSTOFactoryABI = abis.usdTieredSTOFactory(); - let usdTieredSTOFactoryAddress = await contracts.getModuleFactoryAddressByName(securityToken.options.address, gbl.constants.MODULES_TYPES.STO, 'USDTieredSTO'); - let usdTieredSTOFactory = new web3.eth.Contract(usdTieredSTOFactoryABI, usdTieredSTOFactoryAddress); + let usdTieredSTOFactory = new web3.eth.Contract(usdTieredSTOFactoryABI, factoryAddress); usdTieredSTOFactory.setProvider(web3.currentProvider); let stoFee = new web3.utils.BN(await usdTieredSTOFactory.methods.getSetupCost().call()); @@ -558,7 +563,7 @@ async function usdTieredSTO_launch(stoConfig) { addresses.usdToken] ); - let addModuleAction = securityToken.methods.addModule(usdTieredSTOFactoryAddress, bytesSTO, stoFee, 0); + 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}`); @@ -782,6 +787,8 @@ async function usdTieredSTO_configure(currentSTO) { '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) { @@ -840,6 +847,9 @@ async function usdTieredSTO_configure(currentSTO) { await modfifyFunding(currentSTO); await usdTieredSTO_status(currentSTO); break; + case 'Reclaim ETH or ERC20 token from contract': + await reclaimFromContract(currentSTO); + break; } } } @@ -963,6 +973,33 @@ async function modfifyTiers(currentSTO) { 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 // ////////////////////// @@ -972,17 +1009,20 @@ async function getBalance(from, type) { 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) { + 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 = []; @@ -998,7 +1038,10 @@ async function getAllModulesByType(type) { 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)); + 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; diff --git a/CLI/commands/token_manager.js b/CLI/commands/token_manager.js index 8ceb3bb4f..a908a4277 100644 --- a/CLI/commands/token_manager.js +++ b/CLI/commands/token_manager.js @@ -107,27 +107,27 @@ async function displayModules() { if (numPM) { console.log(`Permission Manager Modules:`); - pmModules.map(m => console.log(`- ${m.name} is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + 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} is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + 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} is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + 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} is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + 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} is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); + burnModules.map(m => console.log(`- ${m.name} (${m.version}) is ${(m.archived) ? chalk.yellow('archived') : 'unarchived'} at ${m.address}`)); } } @@ -497,17 +497,19 @@ async function addModule() { } async function pauseModule(modules) { - let options = modules.map(m => `${m.name} (${m.address})`); + 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.ISTO(); + 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 and TM modules can be paused/unpaused`)); + 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); @@ -518,17 +520,19 @@ async function pauseModule(modules) { } async function unpauseModule(modules) { - let options = modules.map(m => `${m.name} (${m.address})`); + 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.ISTO(); + 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 and TM modules can be paused/unpaused`)); + 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); @@ -539,7 +543,7 @@ async function unpauseModule(modules) { } async function archiveModule(modules) { - let options = modules.map(m => `${m.name} (${m.address})`); + 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]); @@ -550,7 +554,7 @@ async function archiveModule(modules) { } async function unarchiveModule(modules) { - let options = modules.map(m => `${m.name} (${m.address})`); + 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]); @@ -561,7 +565,7 @@ async function unarchiveModule(modules) { } async function removeModule(modules) { - let options = modules.map(m => `${m.name} (${m.address})`); + 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]); @@ -572,7 +576,7 @@ async function removeModule(modules) { } async function changeBudget(modules) { - let options = modules.map(m => `${m.name} (${m.address})`); + 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]); @@ -595,13 +599,14 @@ async function showUserInfo(_user) { } async function getAllModules() { - function ModuleInfo(_moduleType, _name, _address, _factoryAddress, _archived, _paused) { + 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 = []; @@ -616,12 +621,16 @@ async function getAllModules() { 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 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)); + + modules.push(new ModuleInfo(type, nameTemp, details[1], details[2], details[3], pausedTemp, versionTemp)); } catch (error) { console.log(error); console.log(chalk.red(` diff --git a/CLI/package.json b/CLI/package.json index cc2773b74..354ecc878 100644 --- a/CLI/package.json +++ b/CLI/package.json @@ -4,11 +4,12 @@ "description": "CLI for Polymath-core", "main": "polymath-cli.js", "scripts": { - "stable_coin": "scripts/stable_coin.sh" + "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", diff --git a/CLI/yarn.lock b/CLI/yarn.lock index 82d60b57f..2cb4b3482 100644 --- a/CLI/yarn.lock +++ b/CLI/yarn.lock @@ -15,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" @@ -125,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" @@ -386,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" @@ -586,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" @@ -963,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" @@ -1717,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" @@ -2318,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" @@ -2333,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"