From 6694a068f55768e107e7691332259f34dd416724 Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 29 Nov 2018 15:57:18 -0300 Subject: [PATCH 1/3] Count transfer manager --- CLI/commands/helpers/contract_abis.js | 5 +++ CLI/commands/transfer_manager.js | 47 ++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index f93f18ac7..0b186f590 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -8,6 +8,7 @@ let cappedSTOABI; let usdTieredSTOABI; let generalTransferManagerABI; let manualApprovalTransferManagerABI; +let countTransferManagerABI; let generalPermissionManagerABI; let polyTokenABI; let cappedSTOFactoryABI; @@ -31,6 +32,7 @@ try { usdTieredSTOABI = JSON.parse(require('fs').readFileSync('./build/contracts/USDTieredSTO.json').toString()).abi; generalTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/GeneralTransferManager.json').toString()).abi; manualApprovalTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/ManualApprovalTransferManager.json').toString()).abi; + countTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/CountTransferManager.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; @@ -78,6 +80,9 @@ module.exports = { manualApprovalTransferManager: function () { return manualApprovalTransferManagerABI; }, + countTransferManager: function () { + return countTransferManagerABI; + }, generalPermissionManager: function () { return generalPermissionManagerABI; }, diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 44ae64ca1..4fdd6286a 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -180,7 +180,7 @@ 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 = tmModules[index].name; + let moduleNameSelected = index != -1 ? tmModules[index].name : 'Return'; switch (moduleNameSelected) { case 'GeneralTransferManager': @@ -202,7 +202,9 @@ async function configExistingModules(tmModules) { )); break; case 'CountTransferManager': - //await countTransferManager(); + currentTransferManager = new web3.eth.Contract(abis.countTransferManager(), tmModules[index].address); + currentTransferManager.setProvider(web3.currentProvider); + await countTransferManager(); break; case 'SingleTradeVolumeRestrictionTM': //currentTransferManager = new web3.eth.Contract(abis.singleTradeVolumeRestrictionTM(), tmModules[index].address); @@ -226,8 +228,14 @@ async function configExistingModules(tmModules) { } async function addTransferManagerModule() { - let options = ['GeneralTransferManager', 'ManualApprovalTransferManager'/*, 'PercentageTransferManager', -'CountTransferManager', 'SingleTradeVolumeRestrictionTM', 'LookupVolumeRestrictionTM'*/]; + let options = [ + 'GeneralTransferManager', + 'ManualApprovalTransferManager', + 'CountTransferManager', + //'PercentageTransferManager', + //'SingleTradeVolumeRestrictionTM', + //'LookupVolumeRestrictionTM'*/ + ]; 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?`)) { @@ -241,11 +249,9 @@ async function addTransferManagerModule() { )); break; case 'CountTransferManager': - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); + 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 'SingleTradeVolumeRestrictionTM': /* @@ -643,6 +649,29 @@ async function getManualBlocking(_from, _to) { return result; } +async function countTransferManager() { + console.log(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 = options[index]; + console.log('Selected:', index != -1 ? optionSelected : 'Return', '\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; + } +} + async function singleTradeVolumeRestrictionTM() { console.log(chalk.blue(`Single Trade Volume Restriction Transfer Manager at ${currentTransferManager.options.address}`)); console.log(); From 2c514b39c02fd19ab04209ff3543735782967cec Mon Sep 17 00:00:00 2001 From: Victor Date: Thu, 29 Nov 2018 17:15:05 -0300 Subject: [PATCH 2/3] Percentage transfer manager --- CLI/commands/helpers/contract_abis.js | 5 ++ CLI/commands/transfer_manager.js | 113 ++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/CLI/commands/helpers/contract_abis.js b/CLI/commands/helpers/contract_abis.js index 0b186f590..8a551b70b 100644 --- a/CLI/commands/helpers/contract_abis.js +++ b/CLI/commands/helpers/contract_abis.js @@ -9,6 +9,7 @@ let usdTieredSTOABI; let generalTransferManagerABI; let manualApprovalTransferManagerABI; let countTransferManagerABI; +let percentageTransferManagerABI; let generalPermissionManagerABI; let polyTokenABI; let cappedSTOFactoryABI; @@ -33,6 +34,7 @@ try { generalTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/GeneralTransferManager.json').toString()).abi; manualApprovalTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/ManualApprovalTransferManager.json').toString()).abi; countTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/CountTransferManager.json').toString()).abi; + percentageTransferManagerABI = JSON.parse(require('fs').readFileSync('./build/contracts/PercentageTransferManager.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; @@ -83,6 +85,9 @@ module.exports = { countTransferManager: function () { return countTransferManagerABI; }, + percentageTransferManager: function () { + return percentageTransferManagerABI; + }, generalPermissionManager: function () { return generalPermissionManagerABI; }, diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 4fdd6286a..c3dc7253f 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -193,19 +193,16 @@ async function configExistingModules(tmModules) { currentTransferManager.setProvider(web3.currentProvider); await manualApprovalTransferManager(); break; - case 'PercentageTransferManager': - //await percentageTransferManager(); - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); - 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 'SingleTradeVolumeRestrictionTM': //currentTransferManager = new web3.eth.Contract(abis.singleTradeVolumeRestrictionTM(), tmModules[index].address); //currentTransferManager.setProvider(web3.currentProvider); @@ -232,7 +229,7 @@ async function addTransferManagerModule() { 'GeneralTransferManager', 'ManualApprovalTransferManager', 'CountTransferManager', - //'PercentageTransferManager', + 'PercentageTransferManager', //'SingleTradeVolumeRestrictionTM', //'LookupVolumeRestrictionTM'*/ ]; @@ -241,18 +238,22 @@ async function addTransferManagerModule() { 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 'PercentageTransferManager': - console.log(chalk.red(` - ********************************* - This option is not yet available. - *********************************` - )); - break; 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; case 'SingleTradeVolumeRestrictionTM': /* let isTransferLimitInPercentage = !!readlineSync.keyInSelect(['In tokens', 'In percentage'], 'How do you want to set the transfer limit? ', {cancel: false}); @@ -672,6 +673,86 @@ async function countTransferManager() { } } +async function percentageTransferManager() { + console.log(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 = options[index]; + console.log('Selected:', index != -1 ? optionSelected : 'Return', '\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': + 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; + + } +} + async function singleTradeVolumeRestrictionTM() { console.log(chalk.blue(`Single Trade Volume Restriction Transfer Manager at ${currentTransferManager.options.address}`)); console.log(); From 343b43208f337ca5e07696f54a4ba90f0aa2944c Mon Sep 17 00:00:00 2001 From: Victor Date: Mon, 3 Dec 2018 19:35:48 -0300 Subject: [PATCH 3/3] modifyWhitelistMulti for PercentageTM --- CLI/commands/common/csv_shared.js | 82 ------------------- CLI/commands/common/csv_sync.js | 39 --------- CLI/commands/transfer_manager.js | 28 ++++++- .../Transfer/PercentageTM/whitelist_data.csv | 10 +++ 4 files changed, 37 insertions(+), 122 deletions(-) delete mode 100644 CLI/commands/common/csv_shared.js delete mode 100644 CLI/commands/common/csv_sync.js create mode 100644 CLI/data/Transfer/PercentageTM/whitelist_data.csv diff --git a/CLI/commands/common/csv_shared.js b/CLI/commands/common/csv_shared.js deleted file mode 100644 index 471db13d1..000000000 --- a/CLI/commands/common/csv_shared.js +++ /dev/null @@ -1,82 +0,0 @@ -var contracts = require('../helpers/contract_addresses'); -var abis = require('../helpers/contract_abis'); -var csv = require('./csv_sync'); - -let BATCH_SIZE = 70; - -async function startScript(tokenSymbol, batchSize) { - if (batchSize) { - BATCH_SIZE = batchSize; - } - let STAddress = await checkST(tokenSymbol); - return new web3.eth.Contract(abis.securityToken(), STAddress); -}; - -async function checkST(tokenSymbol) { - let securityTokenRegistry = await STConnect(); - - return await securityTokenRegistry.methods.getSecurityTokenAddress(tokenSymbol).call({}, function (error, result) { - if (result != "0x0000000000000000000000000000000000000000") { - return result - } else { - console.log("Token doesn't exist") - process.exit(0) - } - }); -} - -async function STConnect() { - try { - let STRegistryAddress = await contracts.securityTokenRegistry(); - let STRegistry = new web3.eth.Contract(abis.securityTokenRegistry(), STRegistryAddress); - return STRegistry; - } catch (err) { - console.log("There was a problem getting the contracts. Make sure they are deployed to the selected network."); - process.exit(0); - } -} - -async function readScript(file, processing) { - let allocData = new Array(); - let distribData = new Array(); - let fullFileData = new Array(); - let badData = new Array(); - let i = 0; - - var CSV_STRING = csv(file); - - CSV_STRING.forEach(line => { - let data_processed = processing(line); - fullFileData.push(data_processed[1]); - - if (data_processed[0]) { - allocData.push(data_processed[1]); - i++; - if (i >= BATCH_SIZE) { - distribData.push(allocData); - allocData = []; - i = 0; - } - } else { - badData.push(data_processed[1]); - } - }); - - distribData.push(allocData); - allocData = []; - - return { - distribData: distribData, - fullFileData: fullFileData, - badData: badData - } -} - -module.exports = { - start: async (tokenSymbol, batchSize) => { - return await startScript(tokenSymbol, batchSize); - }, - read: async (file, processing) => { - return await readScript(file, processing); - } -} \ No newline at end of file diff --git a/CLI/commands/common/csv_sync.js b/CLI/commands/common/csv_sync.js deleted file mode 100644 index 6955a2445..000000000 --- a/CLI/commands/common/csv_sync.js +++ /dev/null @@ -1,39 +0,0 @@ -var fs = require('fs'); - -function load(filename, options) { - var content = fs.readFileSync(filename, 'utf-8'); - var lines = content.split('\n'); - var splitToColumns = options && check.fn(options.getColumns) ? options.getColumns : getColumns; - var results = []; - - lines.forEach(function (line, index) { - if (!line) { - return; - } - - var obj = []; - var values = stripQuotes(splitToColumns(line, index)); - - values.forEach(function (value) { - obj.push(value) - }); - - results.push(obj); - }); - - return results; -} - -function getColumns(line) { - var columns = line.split(','); - return columns; -} - -function stripQuotes(words) { - return words.map(function (word) { - word = word.trim(); - return word.replace(/"/g, ''); - }); -} - -module.exports = load; diff --git a/CLI/commands/transfer_manager.js b/CLI/commands/transfer_manager.js index 79eae49f4..e56233466 100644 --- a/CLI/commands/transfer_manager.js +++ b/CLI/commands/transfer_manager.js @@ -10,6 +10,7 @@ const csvParse = require('./helpers/csv'); /////////////////// // Constants const WHITELIST_DATA_CSV = './CLI/data/Transfer/GTM/whitelist_data.csv'; +const PERCENTAGE_WHITELIST_DATA_CSV = './CLI/data/Transfer/PercentageTM/whitelist_data.csv'; // App flow let tokenSymbol; @@ -518,7 +519,7 @@ async function modifyWhitelistInBatch() { console.log(`Batch ${batch + 1} - Attempting to modify whitelist to accounts: \n\n`, investorArray[batch], '\n'); let action = await currentTransferManager.methods.modifyWhitelistMulti(investorArray[batch], fromTimesArray[batch], toTimesArray[batch], expiryTimeArray[batch], canBuyFromSTOArray[batch]); let receipt = await common.sendTransaction(action); - console.log(chalk.green('Whitelist transaction was successful.')); + 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`); } } @@ -785,6 +786,31 @@ async function percentageTransferManager() { } 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': 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