diff --git a/CLI/commands/ST20Generator.js b/CLI/commands/ST20Generator.js index 24ccdcbbb..530da89bb 100644 --- a/CLI/commands/ST20Generator.js +++ b/CLI/commands/ST20Generator.js @@ -20,7 +20,8 @@ const MODULES_TYPES = { PERMISSION: 1, TRANSFER: 2, STO: 3, - DIVIDENDS: 4 + DIVIDENDS: 4, + BURN: 5 } const cappedSTOFee = 20000; diff --git a/CLI/commands/dividends_manager.js b/CLI/commands/dividends_manager.js index c4f158cd6..fc9caab4e 100644 --- a/CLI/commands/dividends_manager.js +++ b/CLI/commands/dividends_manager.js @@ -18,7 +18,8 @@ const MODULES_TYPES = { PERMISSION: 1, TRANSFER: 2, STO: 3, - DIVIDENDS: 4 + DIVIDENDS: 4, + BURN: 5 } // App flow diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index a505579d2..b6c58cc38 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -7,11 +7,13 @@ let stoInterfaceABI; let cappedSTOABI; let usdTieredSTOABI; let generalTransferManagerABI; +let generalPermissionManagerABI; let polyTokenABI; let cappedSTOFactoryABI; let usdTieredSTOFactoryABI; let erc20DividendCheckpointABI; let etherDividendCheckpointABI; +let moduleInterfaceABI; let ownableABI; let moduleFactoryABI; @@ -25,16 +27,18 @@ try { 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; } catch (err) { console.log('\x1b[31m%s\x1b[0m',"Couldn't find contracts' artifacts. Make sure you ran truffle compile first"); - return; + throw err; } module.exports = { @@ -65,6 +69,9 @@ module.exports = { generalTransferManager: function () { return generalTransferManagerABI; }, + generalPermissionManager: function () { + return generalPermissionManagerABI; + }, polyToken: function () { return polyTokenABI; }, @@ -80,6 +87,9 @@ module.exports = { etherDividendCheckpoint: function () { return etherDividendCheckpointABI; }, + moduleInterface: function () { + return moduleInterfaceABI; + }, ownable: function () { return ownableABI; }, diff --git a/CLI/commands/permission_manager.js b/CLI/commands/permission_manager.js new file mode 100644 index 000000000..49a20f7e3 --- /dev/null +++ b/CLI/commands/permission_manager.js @@ -0,0 +1,222 @@ +var readlineSync = require('readline-sync'); +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'); + +// App flow +let tokenSymbol; +let securityTokenRegistry; +let securityToken; +let generalPermissionManager; + +const MODULES_TYPES = { + PERMISSION: 1, + TRANSFER: 2, + STO: 3, + DIVIDEND: 4, + BURN: 5 +} + +async function executeApp(remoteNetwork) { + await global.initialize(remoteNetwork); + + common.logAsciiBull(); + console.log("***********************************************"); + console.log("Welcome to the Command-Line Permission Manager."); + console.log("***********************************************"); + console.log("Issuer Account: " + Issuer.address + "\n"); + + await setup(); + try { + await selectST(); + await addPermissionModule(); + await changePermissionStep(); + } catch (err) { + console.log(err); + return; + } +}; + +async function setup(){ + try { + let securityTokenRegistryAddress = await contracts.securityTokenRegistry(); + let securityTokenRegistryABI = abis.securityTokenRegistry(); + securityTokenRegistry = new web3.eth.Contract(securityTokenRegistryABI, securityTokenRegistryAddress); + 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."); + process.exit(0); + } +} + +async function selectST() { + if (!tokenSymbol) + tokenSymbol = readlineSync.question('Enter the token symbol: '); + + 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.`)); + await selectST(); + } else { + let securityTokenABI = abis.securityToken(); + securityToken = new web3.eth.Contract(securityTokenABI,result); + } +} + +async function addPermissionModule() { + let generalPermissionManagerAddress; + let result = await securityToken.methods.getModulesByName(web3.utils.toHex('GeneralPermissionManager')).call(); + 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 addModuleAction = securityToken.methods.addModule(permissionManagerFactoryAddress, web3.utils.fromAscii('', 16), 0, 0); + let receipt = await common.sendTransaction(Issuer, addModuleAction, defaultGasPrice); + let event = common.getEventFromLogs(securityToken._jsonInterface, receipt.logs, 'ModuleAdded'); + console.log(`Module deployed at address: ${event._module}`); + generalPermissionManagerAddress = event._module; + } else { + process.exit(0); + } + } else { + generalPermissionManagerAddress = result[0]; + } + + let generalPermissionManagerABI = abis.generalPermissionManager(); + generalPermissionManager = new web3.eth.Contract(generalPermissionManagerABI, generalPermissionManagerAddress); + generalPermissionManager.setProvider(web3.currentProvider); +} + +async function changePermissionStep() { + console.log('\n\x1b[34m%s\x1b[0m',"Permission Manager - Change Permission"); + let selectedDelegate = await selectDelegate(); + let selectedModule = await selectModule(); + let selectedPermission = await selectPermission(selectedModule.permissions); + let isValid = isPermissionValid(); + await changePermission(selectedDelegate, selectedModule.address, selectedPermission, isValid); +} + +// Helper functions +async function selectDelegate() { + let result; + let delegates = await getDelegates(); + + let options = ['Add new delegate']; + options = options.concat(delegates.map(function(d) { + return `Account: ${d.address} + Details: ${d.details}` + })); + + let index = readlineSync.keyInSelect(options, 'Select a delegate:', {cancel: false}); + if (index == 0) { + let newDelegate = await addNewDelegate(); + result = newDelegate; + } else { + result = delegates[index - 1].address; + } + + return result; +} + +async function selectModule() { + let modules = await getModulesWithPermissions(); + let options = modules.map(function(m) { + return m.name; + }); + let index = readlineSync.keyInSelect(options, 'Select a module:', {cancel: false}); + return modules[index]; +} + +async function selectPermission(permissions) { + let options = permissions.map(function(p) { + return p + }); + 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}); + 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, 1.5); + common.getEventFromLogs(generalPermissionManager._jsonInterface, receipt.logs, 'ChangePermission'); + console.log(`Permission changed succesfully,`); +} + +async function getDelegates() { + let result = []; + /* + let events = await generalPermissionManager.getPastEvents('LogAddPermission', { fromBlock: 0}); + for (let event of events) { + let delegate = {}; + delegate.address = event.returnValues._delegate; + delegate.details = web3.utils.hexToAscii(event.returnValues._details); + result.push(delegate); + } + */ + let delegates = await generalPermissionManager.methods.getAllDelegates().call(); + for (let d of delegates) { + let delegate = {}; + delegate.address = d; + delegate.details = web3.utils.hexToAscii(await generalPermissionManager.methods.delegateDetails(d).call()); + result.push(delegate); + } + return result; +} + +async function addNewDelegate() { + let newDelegate = readlineSync.question('Enter the delegate address: ', { + limit: function (input) { + return web3.utils.isAddress(input); + }, + limitMessage: "Must be a valid address" + }); + let details = readlineSync.question('Enter the delegate details (i.e `Belongs to financial firm`): ', { + 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 event = common.getEventFromLogs(generalPermissionManager._jsonInterface, receipt.logs, 'AddDelegate'); + console.log(`Delegate added succesfully: ${event._delegate} - ${event._details}`); + return event._delegate; +} + +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 m of modulesAttached) { + let contractTemp = new web3.eth.Contract(moduleABI, m); + let permissions = await contractTemp.methods.getPermissions().call(); + if (permissions.length > 0) { + 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) }) + }) + } + } + } + + return modules; +} + +module.exports = { + executeApp: async function(remoteNetwork) { + return executeApp(remoteNetwork); + } +} \ No newline at end of file diff --git a/CLI/polymath-cli.js b/CLI/polymath-cli.js index 5ce0a455d..98c8d240d 100644 --- a/CLI/polymath-cli.js +++ b/CLI/polymath-cli.js @@ -11,6 +11,7 @@ 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 yaml = require('js-yaml'); const fs = require('fs'); @@ -142,6 +143,14 @@ program await strMigrator.executeApp(toStrAddress, fromTrAddress, fromStrAddress, program.remoteNode); }); +program + .command('permission_manager') + .alias('pm') + .description('Runs permission_manager') + .action(async function() { + await permission_manager.executeApp(program.remoteNode); + }); + program.parse(process.argv); if (typeof program.commands.length == 0) { diff --git a/README.md b/README.md index fc4498ecf..820fc8cf6 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ You can access Ethereum via the Infura load-balanced nodes. You have to save you node CLI/polymath-cli faucet --remote-node kovan ``` 3. Connected to a local private test network using `ganache-cli`. -You have to save the private key for the first account generated by ganache into `./privKeyLocal`. +You have to save the private key for the one of the accounts generated by ganache into `./privKeyLocal`. ## Poly Faucet