diff --git a/README.md b/README.md index 0145c14..809abc0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![npm version](https://badge.fury.io/js/eoslime.svg)](https://badge.fury.io/js/eoslime.svg) [![codecov](https://codecov.io/gh/LimeChain/eoslime/branch/master/graph/badge.svg)](https://codecov.io/gh/LimeChain/eoslime) +[![support typescript](https://badgen.net/badge/Support/TypeScript/cyan)](https://badgen.net/badge/Support/TypeScript/cyan) eoslime.js ============ @@ -23,6 +24,9 @@ Thanks these wonderful people for helping improve EOSLime
Artem

💡 +
Pedro Reis Colaço

+ 💻 + @@ -30,6 +34,93 @@ Thanks these wonderful people for helping improve EOSLime # Change log +## Version 2.0.0 change log +## [Typescript support && Codebase code coverage] +### Breaking changes +* Rename **Account.addAuthorityKey** to **Account.addOnBehalfKey** +* Rename **Account.executiveAuth** to **Account.authority** +* New way to access contract actions and tables + **Actions** + ``` + const tokenContract = await eoslime.Contract.at('contract name'); + // Before + tokenContract.issue(params, options) + // Now + tokenContract.actions.issue([params], options) + ``` + **Tables** + ``` + const tokenContract = await eoslime.Contract.at('contract name'); + // Before + tokenContract.balances() + // Now + tokenContract.tables.balances() + ``` +* Contract.on('deploy') + ``` + // Before + Contract.on('deploy', (tx, contract) => {})) + // Now + Contract.on('deploy', (contract, tx) => {})) + ``` +* Remove AuthorityAccount +* Deprecate **Account.createSubAuthority** +* Replace **createSubAuthority** with **addAuthority** + ``` + const account = await eoslime.Account.createRandom(); + + // ------------ [ Before ] ------------ + + // Add subAuthority and return an instance of AuthorityAccount + const subAuthorityAccount = await account.createSubAuthority('subauthority'); + + // Add what actions the new authority could access + await subAuthorityAccount.setAuthorityAbilities([ + { action: 'produce', contract: faucetContract.name } + ]); + + // ------------ [ Now ] ------------ + + // Add subAuthority and return tx receipt + const tx = await account.addAuthority('subauthority'); + + // Add what actions the new authority could access + await account.setAuthorityAbilities('subauthority', [ + { action: 'produce', contract: faucetContract.name } + ]); + + const subAuthorityAccount = eoslime.Account.load('name', 'key', 'subauthority'); + ``` + +### News +* Typescript support +* Refactor CLI commands +* Fix nodeos pre-loaded accounts to have different keys +* Unit tests for all CLI commands +* Return transaction receipts on every chain iteraction +* Use logger instead console.log +* Update Kylin network endpoint +* Add Jungle3 support +* Remove the check requiring an executor to be provided on contract instantiation. Without executor, one could fetch only the data from the contract tables +* contract.action.sign(params) + ``` + // Before + contract.action.sign(params) + + // Now + // Options are the same like the ones for contract.action(params, options) + contract.actions.action.sign([params], options) + ``` +* contract.action.getRawTransaction(params) + ``` + // Before + contract.action.getRawTransaction(params) + + // Now + // Options are the same like the ones for contract.action(params, options) + contract.actions.action.getRawTransaction([params], options) + ``` + ## Version 1.0.4 change log * **eoslime nodeos** @@ -91,7 +182,7 @@ EOSLIME was able to be initialized only with pre-configured providers connection const eoslime = require('eoslime').init('bos', { url: 'Your url', chainId: 'Your chainId' }); // ... any other supported netwok ... ``` -* **Allow read-only contracts** - You are able now to instantiate a contract withouth a signer/executor and read the contract's tables +* **Allow read-only contracts** - You are able now to instantiate a contract without a signer/executor and read the contract's tables * **Add Tutorial section in the documentation** * **Describe how examples in the documentation could be run** * **Increase the code coverage from 46% to 90+ %** diff --git a/cli-commands/command-definer.js b/cli-commands/command-definer.js index 8a0c821..b774242 100644 --- a/cli-commands/command-definer.js +++ b/cli-commands/command-definer.js @@ -1,5 +1,5 @@ class CommandDefiner { - constructor(yargs) { + constructor (yargs) { this.yargs = yargs; } @@ -28,8 +28,8 @@ class CommandDefiner { handle (command) { return async (args) => { - const result = await command.execute(args); - if (!result) { + await command.execute(args); + if (!command.hasBeenExecuted) { this.yargs.showHelp(); } } diff --git a/cli-commands/commands/command.js b/cli-commands/commands/command.js index 2aa7277..495a562 100644 --- a/cli-commands/commands/command.js +++ b/cli-commands/commands/command.js @@ -1,9 +1,11 @@ class Command { - constructor(commandDefinition) { + constructor (commandDefinition) { this.subcommands = []; this.options = commandDefinition.options || []; this.template = commandDefinition.template || ''; this.description = commandDefinition.description || ''; + + this.hasBeenExecuted = true; } async processOptions (args) { @@ -21,8 +23,7 @@ class Command { return optionResults; } - execute (args) { } - + async execute (args) { } } module.exports = Command; diff --git a/cli-commands/commands/compile/index.js b/cli-commands/commands/compile/index.js index 675a712..b68ef82 100644 --- a/cli-commands/commands/compile/index.js +++ b/cli-commands/commands/compile/index.js @@ -1,7 +1,9 @@ const AsyncSoftExec = require('../../helpers/async-soft-exec'); const Command = require('../command'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; +const MESSAGE_CONTRACT = require('./messages').CONTRACT; + const compiledDirectories = require('./specific/directories.json'); const compileCommandDefinition = require('./definition'); @@ -11,13 +13,13 @@ const fileSysUtils = require('../../helpers/file-system-util'); class CompileCommand extends Command { - constructor() { + constructor () { super(compileCommandDefinition); } async execute (args) { try { - commandMessages.StartCompilation(); + MESSAGE_COMMAND.Start(); const optionsResults = await super.processOptions(args); @@ -26,20 +28,25 @@ class CompileCommand extends Command { for (let i = 0; i < optionsResults.path.length; i++) { const contractPath = optionsResults.path[i]; - // Todo: Check how to compile without using eosio-cpp - const asyncSoftExec = new AsyncSoftExec(`eosio-cpp -I . -o ./compiled/${contractPath.fileName}.wasm ${contractPath.fullPath} --abigen`); - asyncSoftExec.onError((error) => commandMessages.UnsuccessfulCompilationOfContract(error, contractPath.fileName)); - asyncSoftExec.onSuccess(() => commandMessages.SuccessfulCompilationOfContract(contractPath.fileName)); - - await asyncSoftExec.exec(); + await processCompilation(contractPath) } } else { - commandMessages.ContractNotExisting(); + MESSAGE_CONTRACT.NotFound(); } } catch (error) { - commandMessages.UnsuccessfulCompilation(error); + MESSAGE_COMMAND.Error(error); } - return true; + } +} + +const processCompilation = async function (contract) { + try { + const asyncSoftExec = new AsyncSoftExec(`eosio-cpp -I . -o ./compiled/${contract.fileName}.wasm ${contract.fullPath} --abigen`); + await asyncSoftExec.exec(); + + MESSAGE_CONTRACT.Compiled(contract.fileName); + } catch (error) { + MESSAGE_CONTRACT.NotCompiled(error, contract.fileName); } } diff --git a/cli-commands/commands/compile/messages.js b/cli-commands/commands/compile/messages.js index af94aff..f5f04b2 100644 --- a/cli-commands/commands/compile/messages.js +++ b/cli-commands/commands/compile/messages.js @@ -1,15 +1,13 @@ -const chalk = require('chalk'); +const logger = require('../../common/logger'); module.exports = { - 'StartCompilation': () => { console.log(chalk.magentaBright('===== Compilation has started... =====')); }, - 'UnsuccessfulCompilation': (error) => { - console.log(chalk.redBright(`===== Unsuccessful compilation =====`)); - console.log(error); + 'CONTRACT': { + 'Compiled': (contract) => { logger.success(`===== ${contract} has been successfully compiled =====`); }, + 'NotCompiled': (error, file) => { logger.error(`===== Unsuccessful compilation of ${file} =====`, error); }, + 'NotFound': () => { logger.info(`===== There is not a contract to compile =====`); } }, - 'SuccessfulCompilationOfContract': (contract) => { console.log(chalk.greenBright(`===== Successfully compilation of ${contract} =====`)); }, - 'UnsuccessfulCompilationOfContract': (error, file) => { - console.log(chalk.redBright(`===== Unsuccessful compilation of ${file} =====`)); - console.log(error); - }, - 'ContractNotExisting': () => { console.log(chalk.redBright(`===== There is not a contract to compile =====`)); } + 'COMMAND': { + 'Start': () => { logger.info('===== Compilation has started... ====='); }, + 'Error': (error) => { logger.error(`===== Unsuccessful compilation =====`, error); }, + } } \ No newline at end of file diff --git a/cli-commands/commands/compile/options/path-option.js b/cli-commands/commands/compile/options/path-option.js index 935a823..1035a88 100644 --- a/cli-commands/commands/compile/options/path-option.js +++ b/cli-commands/commands/compile/options/path-option.js @@ -1,10 +1,11 @@ const fileSystemUtil = require('../../../helpers/file-system-util'); +const path = require('path'); const Option = require('../../option'); class PathOption extends Option { - constructor() { + constructor () { super( 'path', { @@ -28,7 +29,7 @@ class PathOption extends Option { }); } - return optionValue.endsWith('.cpp') ? [`${__dirname}/${optionValue}`] : []; + return optionValue.endsWith('.cpp') ? [{ fullPath: optionValue, fileName: path.basename(optionValue, '.cpp') }] : []; } } diff --git a/cli-commands/commands/deploy/index.js b/cli-commands/commands/deploy/index.js index 4c0afdb..217bda2 100644 --- a/cli-commands/commands/deploy/index.js +++ b/cli-commands/commands/deploy/index.js @@ -1,24 +1,26 @@ const Command = require('../command'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; +const MESSAGE_SCRIPT = require('./messages').SCRIPT; + const deployCommandDefinition = require('./definition'); // eoslime deploy --path --network --deployer class DeployCommand extends Command { - constructor() { + constructor () { super(deployCommandDefinition); } async execute (args) { try { - commandMessages.StartDeployment(); + MESSAGE_COMMAND.Start(); + const optionsResults = await super.processOptions(args); await runDeploymentScripts(optionsResults.path, optionsResults.network, optionsResults.deployer); } catch (error) { - commandMessages.UnsuccessfulDeployment(error); + MESSAGE_COMMAND.Error(error); } - return true; } } @@ -27,9 +29,9 @@ const runDeploymentScripts = async function (deploymentScripts, ...configuration const deploymentScript = deploymentScripts[i]; try { await deploymentScript.deploy(...configuration); - commandMessages.SuccessfulDeploymentOfScript(deploymentScript.fileName); + MESSAGE_SCRIPT.Processed(deploymentScript.fileName); } catch (error) { - commandMessages.UnsuccessfulDeploymentOfScript(deploymentScript.fileName, error); + MESSAGE_SCRIPT.NotProcessed(deploymentScript.fileName, error); } } } diff --git a/cli-commands/commands/deploy/messages.js b/cli-commands/commands/deploy/messages.js index b76746d..7df124d 100644 --- a/cli-commands/commands/deploy/messages.js +++ b/cli-commands/commands/deploy/messages.js @@ -1,14 +1,12 @@ -const chalk = require('chalk'); +const logger = require('../../common/logger'); module.exports = { - 'StartDeployment': () => { console.log(chalk.magentaBright('===== Deployment has started... =====')); }, - 'SuccessfulDeploymentOfScript': (script) => { console.log(chalk.greenBright(`===== Successful deployment of ${script} =====`)); }, - 'UnsuccessfulDeploymentOfScript': (script, error) => { - console.log(chalk.redBright(`===== Unsuccessful deployment of ${script} =====`)); - console.log(error); + 'SCRIPT': { + 'Processed': (script) => { logger.success(`===== Successful deployment of ${script} =====`); }, + 'NotProcessed': (script, error) => { logger.error(`===== Unsuccessful deployment of ${script} =====`, error); }, }, - 'UnsuccessfulDeployment': (error) => { - console.log(chalk.redBright(`===== Unsuccessful deployment =====`)); - console.log(error); + 'COMMAND': { + 'Start': () => { logger.info('===== Deployment has started... ====='); }, + 'Error': (error) => { logger.error(`===== Unsuccessful deployment =====`, error); } } } \ No newline at end of file diff --git a/cli-commands/commands/deploy/options/network-option.js b/cli-commands/commands/deploy/options/network-option.js index e8b11a8..4f9cc94 100644 --- a/cli-commands/commands/deploy/options/network-option.js +++ b/cli-commands/commands/deploy/options/network-option.js @@ -2,7 +2,7 @@ const Option = require('../../option'); const eoslime = require('../../../../index'); class NetworkOption extends Option { - constructor() { + constructor () { super( 'network', { @@ -15,9 +15,7 @@ class NetworkOption extends Option { } process (optionValue) { - if (optionValue) { - return eoslime.init(optionValue); - } + return eoslime.init(optionValue); } } diff --git a/cli-commands/commands/deploy/options/path-option.js b/cli-commands/commands/deploy/options/path-option.js index 710849d..c98f262 100644 --- a/cli-commands/commands/deploy/options/path-option.js +++ b/cli-commands/commands/deploy/options/path-option.js @@ -4,7 +4,7 @@ const Option = require('../../option'); const fileSystemUtil = require('../../../helpers/file-system-util'); class PathOption extends Option { - constructor() { + constructor () { super( 'path', { @@ -16,8 +16,8 @@ class PathOption extends Option { } async process (optionValue) { - let deploymentFilesFunctions = []; if (fileSystemUtil.isDir(optionValue)) { + const deploymentFilesFunctions = []; const dirFiles = await fileSystemUtil.recursivelyReadDir(optionValue); for (let i = 0; i < dirFiles.length; i++) { @@ -27,16 +27,16 @@ class PathOption extends Option { deploy: require(path.resolve('./', dirFile.fullPath)) }); } + + return deploymentFilesFunctions; } if (fileSystemUtil.isFile(optionValue)) { - deploymentFilesFunctions.push({ + return [{ fileName: optionValue, deploy: require(path.resolve('./', optionValue)) - }); + }]; } - - return deploymentFilesFunctions; } } diff --git a/cli-commands/commands/group-command.js b/cli-commands/commands/group-command.js index 639564d..ab6ffde 100644 --- a/cli-commands/commands/group-command.js +++ b/cli-commands/commands/group-command.js @@ -1,17 +1,17 @@ const Command = require('./command'); class GroupCommand extends Command { - constructor(commandDefinition) { + constructor (commandDefinition) { super(commandDefinition); } async execute (args) { if (optionsProvided(args, this.options)) { await super.processOptions(args); - return true; + this.hasBeenExecuted = true; + } else { + this.hasBeenExecuted = false; } - - return false; } } diff --git a/cli-commands/commands/init/index.js b/cli-commands/commands/init/index.js index 3385bbe..05d47e3 100644 --- a/cli-commands/commands/init/index.js +++ b/cli-commands/commands/init/index.js @@ -4,7 +4,7 @@ const Command = require('../command'); const initDirectories = require('./specific/directories.json'); const initCommandDefinition = require('./definition'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; const fileSystemUtil = require('../../helpers/file-system-util'); const defaultPackageJsonDestination = `${__dirname}/specific/default-package.json`; @@ -13,47 +13,45 @@ const defaultPackageJsonDestination = `${__dirname}/specific/default-package.jso class InitCommand extends Command { - constructor() { + constructor () { super(initCommandDefinition); } async execute (args) { + try { - commandMessages.Installation(); + MESSAGE_COMMAND.Start(); createContractsDir(); createDeploymentDir(); createTestsDir(); copyDefaultPackageJson(); - super.processOptions(args); - } catch (error) { - commandMessages.UnsuccessfulInstallation(error); - } - - const asyncSoftExec = new AsyncSoftExec('npm install eoslime'); - asyncSoftExec.onError((error) => { commandMessages.UnsuccessfulInstallation(error); }); - asyncSoftExec.onSuccess(() => { commandMessages.SuccessfulInstallation(); }); + await super.processOptions(args); - await asyncSoftExec.exec(); + const asyncSoftExec = new AsyncSoftExec('npm install eoslime'); + await asyncSoftExec.exec(); - return true; + MESSAGE_COMMAND.Success(); + } catch (error) { + MESSAGE_COMMAND.Error(error); + } } } -let createContractsDir = function () { +const createContractsDir = function () { fileSystemUtil.createDir(initDirectories.CONTRACTS); } -let createDeploymentDir = function () { +const createDeploymentDir = function () { fileSystemUtil.createDir(initDirectories.DEPLOYMENT); } -let createTestsDir = function () { +const createTestsDir = function () { fileSystemUtil.createDir(initDirectories.TESTS); } -let copyDefaultPackageJson = function () { +const copyDefaultPackageJson = function () { fileSystemUtil.copyFileFromTo(defaultPackageJsonDestination, initDirectories.PACKAGE_JSON); } diff --git a/cli-commands/commands/init/messages.js b/cli-commands/commands/init/messages.js index 7b3967a..7fbee95 100644 --- a/cli-commands/commands/init/messages.js +++ b/cli-commands/commands/init/messages.js @@ -1,10 +1,9 @@ -const chalk = require('chalk'); +const logger = require('../../common/logger'); module.exports = { - 'Installation': () => { console.log(chalk.magentaBright('===== Installing eoslime... =====')); }, - 'SuccessfulInstallation': () => { console.log(chalk.greenBright('===== Successfully installed =====')); }, - 'UnsuccessfulInstallation': (error) => { - console.log(chalk.redBright('===== Unsuccessful installation =====')); - console.log(error); + 'COMMAND': { + 'Start': () => { logger.info('===== Installing eoslime... ====='); }, + 'Success': () => { logger.success('===== Successfully installed ====='); }, + 'Error': (error) => { logger.error('===== Unsuccessful installation =====', error); } } -} \ No newline at end of file +} diff --git a/cli-commands/commands/init/options/with-example/tests-example/tests.js b/cli-commands/commands/init/options/with-example/tests-example/tests.js index d850183..b34f78b 100644 --- a/cli-commands/commands/init/options/with-example/tests-example/tests.js +++ b/cli-commands/commands/init/options/with-example/tests-example/tests.js @@ -32,7 +32,7 @@ describe("EOSIO Token", function (eoslime) { }); it("Should create a new token", async () => { - await tokenContract.create(tokensIssuer.name, TOTAL_SUPPLY); + await tokenContract.actions.create([tokensIssuer.name, TOTAL_SUPPLY]); /* You have access to the EOS(eosjs) instance: @@ -45,25 +45,25 @@ describe("EOSIO Token", function (eoslime) { }); it("Should issue tokens", async () => { - await tokenContract.create(tokensIssuer.name, TOTAL_SUPPLY); + await tokenContract.actions.create([tokensIssuer.name, TOTAL_SUPPLY]); /* On each contract method you can provide an optional object -> { from: account } If you don't provide a 'from' object, the method will be executed from the contract account authority */ - await tokenContract.issue(tokensHolder.name, HOLDER_SUPPLY, "memo", { from: tokensIssuer }); + await tokenContract.actions.issue([tokensHolder.name, HOLDER_SUPPLY, "memo"], { from: tokensIssuer }); let holderBalance = await tokensHolder.getBalance("SYS", tokenContract.name); assert.equal(holderBalance[0], HOLDER_SUPPLY, "Incorrect holder balance"); }); it("Should throw if tokens quantity is negative", async () => { - await tokenContract.create(tokensIssuer.name, TOTAL_SUPPLY); + await tokenContract.actions.create([tokensIssuer.name, TOTAL_SUPPLY]); const INVALID_ISSUING_AMOUNT = "-100.0000 SYS"; /* For easier testing, eoslime provides you ('utils.test') with helper functions */ - await eoslime.tests.expectAssert(tokenContract.issue(tokensHolder.name, INVALID_ISSUING_AMOUNT, "memo", { from: tokensIssuer })); + await eoslime.tests.expectAssert(tokenContract.actions.issue([tokensHolder.name, INVALID_ISSUING_AMOUNT, "memo"], { from: tokensIssuer })); let holderBalance = await tokensHolder.getBalance("SYS", tokenContract.name); assert.equal(holderBalance.length, 0, "Incorrect holder balance"); diff --git a/cli-commands/commands/init/options/with-example/with-example-option.js b/cli-commands/commands/init/options/with-example/with-example-option.js index ee57beb..e41a75c 100644 --- a/cli-commands/commands/init/options/with-example/with-example-option.js +++ b/cli-commands/commands/init/options/with-example/with-example-option.js @@ -5,7 +5,7 @@ const Option = require('../../../option'); class WithExampleOption extends Option { - constructor() { + constructor () { super( 'with-example', { diff --git a/cli-commands/commands/nodeos/specific/nodeos-data/data-manager.js b/cli-commands/commands/nodeos/specific/nodeos-data/data-manager.js index e259471..e641c34 100644 --- a/cli-commands/commands/nodeos/specific/nodeos-data/data-manager.js +++ b/cli-commands/commands/nodeos/specific/nodeos-data/data-manager.js @@ -14,8 +14,7 @@ const nodeosDataManager = { try { const pid = fileSystemUtil.readFile(path + '/eosd.pid').toString(); return process.kill(pid, 0); - } - catch (e) { + } catch (e) { return e.code === 'EPERM' } }, diff --git a/cli-commands/commands/nodeos/subcommands/accounts/index.js b/cli-commands/commands/nodeos/subcommands/accounts/index.js index 046b74f..04a301e 100644 --- a/cli-commands/commands/nodeos/subcommands/accounts/index.js +++ b/cli-commands/commands/nodeos/subcommands/accounts/index.js @@ -1,7 +1,7 @@ const AccountsTable = require('./specific/accounts-table'); const Command = require('../../../command'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; const accountsCommandDefinition = require('./definition'); const predefinedAccounts = require('../common/accounts'); @@ -9,13 +9,13 @@ const predefinedAccounts = require('../common/accounts'); // eoslime nodeos show accounts class AccountsCommand extends Command { - constructor() { + constructor () { super(accountsCommandDefinition); } async execute (args) { try { - commandMessages.PreloadedAccounts(); + MESSAGE_COMMAND.Start(); const accountsTable = new AccountsTable(); const accounts = predefinedAccounts.accounts(); @@ -26,10 +26,8 @@ class AccountsCommand extends Command { accountsTable.draw(); } catch (error) { - commandMessages.UnsuccessfulShowing(error); + MESSAGE_COMMAND.Error(error); } - - return true; } } diff --git a/cli-commands/commands/nodeos/subcommands/accounts/messages.js b/cli-commands/commands/nodeos/subcommands/accounts/messages.js index eb325c6..a80bd08 100644 --- a/cli-commands/commands/nodeos/subcommands/accounts/messages.js +++ b/cli-commands/commands/nodeos/subcommands/accounts/messages.js @@ -1,9 +1,8 @@ -const chalk = require('chalk'); +const logger = require('../../../../common/logger'); module.exports = { - 'PreloadedAccounts': () => { console.log(chalk.magentaBright('===== Preloaded accounts =====')); }, - 'UnsuccessfulShowing': (error) => { - console.log(chalk.redBright(`===== Accounts has not been shown =====`)); - console.log(error); + 'COMMAND': { + 'Start': () => { logger.info('===== Preloaded accounts ====='); }, + 'Error': (error) => { logger.error(`===== Accounts has not been shown =====`, error); } } } \ No newline at end of file diff --git a/cli-commands/commands/nodeos/subcommands/common/accounts.js b/cli-commands/commands/nodeos/subcommands/common/accounts.js index c778d90..2f9692c 100644 --- a/cli-commands/commands/nodeos/subcommands/common/accounts.js +++ b/cli-commands/commands/nodeos/subcommands/common/accounts.js @@ -8,13 +8,13 @@ const preDefinedAccounts = [ }, { "name": "eoslimekevin", - "publicKey": "EOS7UyV15G2t47MqRm4WpUP6KTfy9sNU3HHGu9aAgR2A3ktxoBTLv", - "privateKey": "5KS9t8LGsaQZxLP6Ln5WK6XwYU8M3AYHcfx1x6zoGmbs34vQsPT" + "publicKey": "EOS6Zz4iPbjm6FNys1zUMaRE4zPXrHcX3SRG65YWneVbdXQTSiqDp", + "privateKey": "5KieRy975NgHk5XQfn8r6o3pcqJDF2vpeV9bDiuB5uF4xKCTwRF" }, { "name": "eoslimemarty", - "publicKey": "EOS7UyV15G2t47MqRm4WpUP6KTfy9sNU3HHGu9aAgR2A3ktxoBTLv", - "privateKey": "5KS9t8LGsaQZxLP6Ln5WK6XwYU8M3AYHcfx1x6zoGmbs34vQsPT" + "publicKey": "EOS7FDeYdY3G8yMNxtrU8MSYnAJc3ZogYHgL7RG3rBf8ZDYA3xthi", + "privateKey": "5JtbCXgK5NERDdFdrmxb8rpYMkoxVfSyH1sR6TYxHBG5zNLHfj5" } ] diff --git a/cli-commands/commands/nodeos/subcommands/logs/index.js b/cli-commands/commands/nodeos/subcommands/logs/index.js index d1f1ef9..106f2f8 100644 --- a/cli-commands/commands/nodeos/subcommands/logs/index.js +++ b/cli-commands/commands/nodeos/subcommands/logs/index.js @@ -3,20 +3,21 @@ const nodoesDataManager = require('../../specific/nodeos-data/data-manager'); const Command = require('../../../command'); const AsyncSoftExec = require('../../../../helpers/async-soft-exec'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; +const MESSAGE_LOGS = require('./messages').LOGS; const logsCommandDefinition = require('./definition'); // eoslime nodeos show logs --lines class LogsCommand extends Command { - constructor() { + constructor () { super(logsCommandDefinition); } async execute (args) { if (!nodoesDataManager.nodeosIsRunning(nodoesDataManager.nodeosPath())) { - commandMessages.EmptyLogs(); - return true; + MESSAGE_LOGS.Empty(); + return void 0; } try { @@ -24,13 +25,12 @@ class LogsCommand extends Command { const nodeosLogFile = nodoesDataManager.nodeosPath() + '/nodeos.log'; const asyncSoftExec = new AsyncSoftExec(`tail -n ${optionsResults.lines} ${nodeosLogFile}`); - asyncSoftExec.onSuccess((logs) => { commandMessages.NodeosLogs(logs); }); - await asyncSoftExec.exec(); + const logs = await asyncSoftExec.exec(); + + MESSAGE_COMMAND.Success(logs); } catch (error) { - commandMessages.UnsuccessfulShowing(error); + MESSAGE_COMMAND.Error(error); } - - return true; } } diff --git a/cli-commands/commands/nodeos/subcommands/logs/messages.js b/cli-commands/commands/nodeos/subcommands/logs/messages.js index 2ea92a0..adeaae1 100644 --- a/cli-commands/commands/nodeos/subcommands/logs/messages.js +++ b/cli-commands/commands/nodeos/subcommands/logs/messages.js @@ -1,15 +1,11 @@ -const chalk = require('chalk'); +const logger = require('../../../../common/logger'); module.exports = { - 'NodeosLogs': (logs) => { - console.log(chalk.magentaBright('===== Nodeos logs =====')); - console.log(logs); + 'COMMAND': { + 'Success': (logs) => { logger.success(`===== Nodeos logs ===== \n ${logs}`); }, + 'Error': (error) => { logger.error(`===== Logs has not been shown =====`, error); } }, - 'EmptyLogs': () => { - console.log(chalk.blueBright('===== Empty logs =====')); - }, - 'UnsuccessfulShowing': (error) => { - console.log(chalk.redBright(`===== Logs has not been shown =====`)); - console.log(error); + 'LOGS': { + 'Empty': () => { logger.info('===== Empty logs ====='); }, } -} \ No newline at end of file +} diff --git a/cli-commands/commands/nodeos/subcommands/start/index.js b/cli-commands/commands/nodeos/subcommands/start/index.js index 242fcb1..6a6f7fc 100644 --- a/cli-commands/commands/nodeos/subcommands/start/index.js +++ b/cli-commands/commands/nodeos/subcommands/start/index.js @@ -1,7 +1,8 @@ const Command = require('../../../command'); const AsyncSoftExec = require('../../../../helpers/async-soft-exec'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; +const MESSAGE_NODEOS = require('./messages').NODEOS; const startCommandDefinition = require('./definition'); const template = require('./specific/template'); @@ -12,20 +13,20 @@ const nodeosDataManager = require('../../specific/nodeos-data/data-manager'); // eoslime nodeos start --path class StartCommand extends Command { - constructor() { + constructor () { super(startCommandDefinition); } async execute (args) { if (nodeosDataManager.nodeosIsRunning(nodeosDataManager.nodeosPath())) { - commandMessages.NodeosAlreadyRunning(); - return true; + MESSAGE_NODEOS.AlreadyRunning(); + return void 0; } try { - commandMessages.StartingNodeos(); - const optionsResults = await super.processOptions(args); + MESSAGE_COMMAND.Start(); + const optionsResults = await super.processOptions(args); nodeosDataManager.setNodeosPath(optionsResults.path); const asyncSoftExec = new AsyncSoftExec(template.build(optionsResults.path)); @@ -34,12 +35,10 @@ class StartCommand extends Command { nodeosDataManager.requireRunningNodeos(optionsResults.path); await predefinedAccounts.load(); - commandMessages.SuccessfullyStarted(); + MESSAGE_COMMAND.Success(); } catch (error) { - commandMessages.UnsuccessfulStarting(error); + MESSAGE_COMMAND.Error(error); } - - return true; } } diff --git a/cli-commands/commands/nodeos/subcommands/start/messages.js b/cli-commands/commands/nodeos/subcommands/start/messages.js index 94119a1..9009b50 100644 --- a/cli-commands/commands/nodeos/subcommands/start/messages.js +++ b/cli-commands/commands/nodeos/subcommands/start/messages.js @@ -1,11 +1,12 @@ -const chalk = require('chalk'); +const logger = require('../../../../common/logger'); module.exports = { - 'StartingNodeos': () => { console.log(chalk.magentaBright('===== Starting nodeos ... =====')); }, - 'NodeosAlreadyRunning': () => { console.log(chalk.blueBright(`===== Nodeos is already running =====`)); }, - 'SuccessfullyStarted': () => { console.log(chalk.greenBright(`===== Successfully started =====`)); }, - 'UnsuccessfulStarting': (error) => { - console.log(chalk.redBright(`===== Nodeos has not been started =====`)); - console.log(error); + 'COMMAND': { + 'Start': () => { logger.info('===== Starting nodeos ... ====='); }, + 'Success': () => { logger.success(`===== Successfully started =====`); }, + 'Error': (error) => { logger.error(`===== Nodeos has not been started =====`, error); } + }, + 'NODEOS': { + 'AlreadyRunning': () => { logger.info(`===== Nodeos is already running =====`); }, } } \ No newline at end of file diff --git a/cli-commands/commands/nodeos/subcommands/start/options/path-option.js b/cli-commands/commands/nodeos/subcommands/start/options/path-option.js index d4a7a43..617e842 100644 --- a/cli-commands/commands/nodeos/subcommands/start/options/path-option.js +++ b/cli-commands/commands/nodeos/subcommands/start/options/path-option.js @@ -1,9 +1,10 @@ const Option = require('../../../../option'); +const fileSystemUtils = require('../../../../../helpers/file-system-util'); const nodeosDataManager = require('../../../specific/nodeos-data/data-manager'); class PathOption extends Option { - constructor() { + constructor () { super( 'path', { @@ -15,7 +16,9 @@ class PathOption extends Option { } async process (optionValue) { - return optionValue; + if (fileSystemUtils.isDir(optionValue)) { + return optionValue; + } } } diff --git a/cli-commands/commands/nodeos/subcommands/stop/index.js b/cli-commands/commands/nodeos/subcommands/stop/index.js index 816ef51..9313fdb 100644 --- a/cli-commands/commands/nodeos/subcommands/stop/index.js +++ b/cli-commands/commands/nodeos/subcommands/stop/index.js @@ -4,19 +4,19 @@ const nodeosDataManager = require('../../specific/nodeos-data/data-manager'); const Command = require('../../../command'); const AsyncSoftExec = require('../../../../helpers/async-soft-exec'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; const stopCommandDefinition = require('./definition'); // eoslime nodeos stop class StopCommand extends Command { - constructor() { + constructor () { super(stopCommandDefinition); } async execute (args) { try { - commandMessages.StoppingNodeos(); + MESSAGE_COMMAND.Start(); if (nodeosDataManager.nodeosIsRunning(nodeosDataManager.nodeosPath())) { const nodeosPid = fileSystemUtil.readFile( @@ -27,12 +27,11 @@ class StopCommand extends Command { } await clearNodeosData(nodeosDataManager.nodeosPath()); - commandMessages.SuccessfullyStopped(); + + MESSAGE_COMMAND.Success(); } catch (error) { - commandMessages.UnsuccessfulStopping(error); + MESSAGE_COMMAND.Error(error); } - - return true; } } diff --git a/cli-commands/commands/nodeos/subcommands/stop/messages.js b/cli-commands/commands/nodeos/subcommands/stop/messages.js index 7af7b3f..4bd98c8 100644 --- a/cli-commands/commands/nodeos/subcommands/stop/messages.js +++ b/cli-commands/commands/nodeos/subcommands/stop/messages.js @@ -1,10 +1,9 @@ -const chalk = require('chalk'); +const logger = require('../../../../common/logger'); module.exports = { - 'StoppingNodeos': () => { console.log(chalk.magentaBright('===== Stopping nodeos ... =====')); }, - 'SuccessfullyStopped': () => { console.log(chalk.greenBright(`===== Successfully stopped =====`)); }, - 'UnsuccessfulStopping': (error) => { - console.log(chalk.redBright(`===== Nodeos has not been stopped =====`)); - console.log(error); + 'COMMAND': { + 'Start': () => { logger.info('===== Stopping nodeos ... ====='); }, + 'Success': () => { logger.success(`===== Successfully stopped =====`); }, + 'Error': (error) => { logger.error(`===== Nodeos has not been stopped =====`, error); } } -} \ No newline at end of file +} diff --git a/cli-commands/commands/shape/index.js b/cli-commands/commands/shape/index.js index d8c3df6..83169d1 100644 --- a/cli-commands/commands/shape/index.js +++ b/cli-commands/commands/shape/index.js @@ -2,33 +2,28 @@ const git = require('simple-git/promise')() const Command = require('../command'); -const commandMessages = require('./messages'); +const MESSAGE_COMMAND = require('./messages').COMMAND; + const shapeCommandDefinition = require('./definition'); // eoslime shape --name class ShapeCommand extends Command { - constructor() { + constructor () { super(shapeCommandDefinition); } async execute (args) { try { - commandMessages.StartShaping(); - const optionsResults = await super.processOptions(args); - - if (!optionsResults.framework) { - commandMessages.InvalidShapeName(args.framework); - } + MESSAGE_COMMAND.Start(); + const optionsResults = await super.processOptions(args); await git.clone(optionsResults.framework); - commandMessages.SuccessfulShaping(); + MESSAGE_COMMAND.Success(); } catch (error) { - commandMessages.UnsuccessfulShaping(error); + MESSAGE_COMMAND.Error(error); } - - return true; } } diff --git a/cli-commands/commands/shape/messages.js b/cli-commands/commands/shape/messages.js index 975c899..8fa1288 100644 --- a/cli-commands/commands/shape/messages.js +++ b/cli-commands/commands/shape/messages.js @@ -1,11 +1,9 @@ -const chalk = require('chalk'); +const logger = require('../../common/logger'); module.exports = { - 'StartShaping': () => { console.log(chalk.magentaBright('===== Shaping of DApp has started... =====')); }, - 'SuccessfulShaping': () => { console.log(chalk.greenBright(`===== Successful shaping =====`)); }, - 'UnsuccessfulShaping': (error) => { - console.log(chalk.redBright(`===== Unsuccessful shaping =====`)); - console.log(error); - }, - 'InvalidShapeName': (name) => { console.log(chalk.redBright(`===== Invalid shape name ${name} =====`)); }, -} \ No newline at end of file + 'COMMAND': { + 'Start': () => { logger.info('===== Shaping of DApp has started... ====='); }, + 'Success': () => { logger.success(`===== Successful shaping =====`); }, + 'Error': (error) => { logger.error(`===== Unsuccessful shaping =====`, error); }, + } +} diff --git a/cli-commands/commands/shape/options/framework-option.js b/cli-commands/commands/shape/options/framework-option.js index 763b762..3f19d2d 100644 --- a/cli-commands/commands/shape/options/framework-option.js +++ b/cli-commands/commands/shape/options/framework-option.js @@ -2,7 +2,7 @@ const Option = require('../../option'); const repositories = require('../specific/repositories.json'); class FrameworkOption extends Option { - constructor() { + constructor () { super( 'framework', { @@ -14,6 +14,10 @@ class FrameworkOption extends Option { } process (optionValue) { + if (!repositories[optionValue]) { + throw new Error('Invalid Shape framework'); + } + return repositories[optionValue]; } } diff --git a/cli-commands/commands/test/definition.js b/cli-commands/commands/test/definition.js index 165c588..d3d0ec1 100644 --- a/cli-commands/commands/test/definition.js +++ b/cli-commands/commands/test/definition.js @@ -1,6 +1,6 @@ const pathOption = require('./options/path-option'); const networkOption = require('./options/network-option'); -const resourceReportOption = require('./options/resource-usage-option/resource-usage-option'); +const resourceReportOption = require('./options/resource-usage-option'); module.exports = { "template": "test", diff --git a/cli-commands/commands/test/index.js b/cli-commands/commands/test/index.js index 64777db..f975306 100644 --- a/cli-commands/commands/test/index.js +++ b/cli-commands/commands/test/index.js @@ -1,21 +1,24 @@ const Command = require('../command'); + +const MESSAGE_COMMAND = require('./messages').COMMAND; const testCommandDefinition = require('./definition'); const eoslime = require('../../../index'); - const testUtils = require('./specific/utils'); // eoslime test --path --network --resource-usage= class TestCommand extends Command { - constructor(TestFramework) { + constructor (TestFramework) { super(testCommandDefinition); this.TestFramework = TestFramework; } async execute (args) { try { + MESSAGE_COMMAND.Start(); + args.eoslime = eoslime.init(); args.testFramework = new this.TestFramework(); @@ -24,12 +27,12 @@ class TestCommand extends Command { setTestsHelpers(args.eoslime); args.testFramework.setDescribeArgs(args.eoslime); - args.testFramework.runTests(); + await args.testFramework.runTests(); + + MESSAGE_COMMAND.Success(); } catch (error) { - console.log(error); + MESSAGE_COMMAND.Error(error); } - - return true; } } diff --git a/cli-commands/commands/test/messages.js b/cli-commands/commands/test/messages.js index 3f29265..5c0618a 100644 --- a/cli-commands/commands/test/messages.js +++ b/cli-commands/commands/test/messages.js @@ -1,14 +1,9 @@ -const chalk = require('chalk'); +const logger = require('../../common/logger'); module.exports = { - 'StartTesting': () => { console.log(chalk.magentaBright('===== Deployment has started... =====')); }, - 'SuccessfulDeploymentOfScript': (script) => { console.log(chalk.greenBright(`===== Successful deployment of ${script} =====`)); }, - 'UnsuccessfulDeploymentOfScript': (script, error) => { - console.log(chalk.redBright(`===== Unsuccessful deployment of ${script} =====`)); - console.log(error); - }, - 'UnsuccessfulDeployment': (error) => { - console.log(chalk.redBright(`===== Unsuccessful deployment =====`)); - console.log(error); + 'COMMAND': { + 'Start': () => { logger.info('===== Testing has started... ====='); }, + 'Success': () => { logger.success(`===== Testing completed successfully =====`); }, + 'Error': (error) => { logger.error(`===== Testing failed =====`, error); } } -} \ No newline at end of file +} diff --git a/cli-commands/commands/test/options/network-option.js b/cli-commands/commands/test/options/network-option.js index a37eeb8..f375839 100644 --- a/cli-commands/commands/test/options/network-option.js +++ b/cli-commands/commands/test/options/network-option.js @@ -1,7 +1,7 @@ const Option = require('../../option'); class NetworkOption extends Option { - constructor() { + constructor () { super( 'network', { diff --git a/cli-commands/commands/test/options/path-option.js b/cli-commands/commands/test/options/path-option.js index d4465ee..d8fd653 100644 --- a/cli-commands/commands/test/options/path-option.js +++ b/cli-commands/commands/test/options/path-option.js @@ -4,7 +4,7 @@ const Option = require('../../option'); const fileSystemUtil = require('../../../helpers/file-system-util'); class PathOption extends Option { - constructor() { + constructor () { super( 'path', { diff --git a/cli-commands/commands/test/options/resource-usage-option/resource-usage-option.js b/cli-commands/commands/test/options/resource-usage-option/index.js similarity index 69% rename from cli-commands/commands/test/options/resource-usage-option/resource-usage-option.js rename to cli-commands/commands/test/options/resource-usage-option/index.js index 27a13b1..e545701 100644 --- a/cli-commands/commands/test/options/resource-usage-option/resource-usage-option.js +++ b/cli-commands/commands/test/options/resource-usage-option/index.js @@ -2,7 +2,7 @@ const Option = require('../../../option'); const ReportTable = require('./report-table'); class ResourceReportOption extends Option { - constructor() { + constructor () { super( 'resource-usage', { @@ -60,7 +60,7 @@ class ResourceReportOption extends Option { } const fillDeploymentsResources = function (eoslime, deploymentsResources) { - eoslime.Contract.on('deploy', (txReceipts, contract) => { + eoslime.Contract.on('deploy', (contract, txReceipts) => { const setCodeResources = extractResourcesCostsFromReceipt(txReceipts[0]); const setABIResources = extractResourcesCostsFromReceipt(txReceipts[1]); @@ -81,36 +81,31 @@ const fillDeploymentsResources = function (eoslime, deploymentsResources) { const fillFunctionsResources = function (eoslime, contractsResources) { eoslime.Contract.on('init', (contract) => { - for (const functionName in contract) { - if (contract.hasOwnProperty(functionName)) { - const contractFunction = contract[functionName]; - - if (contractFunction.isTransactional) { - contractFunction.on('processed', (txReceipt, inputParams) => { - const usedResources = extractResourcesCostsFromReceipt(txReceipt); - if (!contractsResources[contract.name]) { - contractsResources[contract.name] = { - functions: {} - } - } - - if (!contractsResources[contract.name].functions[functionName]) { - contractsResources[contract.name].functions[functionName] = { - calls: 0, - ram: [], - cpu: [], - net: [] - } - } - - const functionResources = contractsResources[contract.name].functions[functionName]; - functionResources.ram = getMinMaxPair(functionResources.ram[0], functionResources.ram[1], usedResources.ramCost); - functionResources.cpu = getMinMaxPair(functionResources.cpu[0], functionResources.cpu[1], usedResources.cpuCost); - functionResources.net = getMinMaxPair(functionResources.net[0], functionResources.net[1], usedResources.netCost); - functionResources.calls += 1; - }); + for (const functionName in contract.actions) { + const contractFunction = contract.actions[functionName]; + contractFunction.on('processed', (txReceipt, inputParams) => { + const usedResources = extractResourcesCostsFromReceipt(txReceipt); + if (!contractsResources[contract.name]) { + contractsResources[contract.name] = { + functions: {} + } } - } + + if (!contractsResources[contract.name].functions[functionName]) { + contractsResources[contract.name].functions[functionName] = { + calls: 0, + ram: [], + cpu: [], + net: [] + } + } + + const functionResources = contractsResources[contract.name].functions[functionName]; + functionResources.ram = getMinMaxPair(functionResources.ram[0], functionResources.ram[1], usedResources.ramCost); + functionResources.cpu = getMinMaxPair(functionResources.cpu[0], functionResources.cpu[1], usedResources.cpuCost); + functionResources.net = getMinMaxPair(functionResources.net[0], functionResources.net[1], usedResources.netCost); + functionResources.calls += 1; + }); } }); } diff --git a/cli-commands/commands/test/specific/utils/index.js b/cli-commands/commands/test/specific/utils/index.js index 79c592d..94635c6 100644 --- a/cli-commands/commands/test/specific/utils/index.js +++ b/cli-commands/commands/test/specific/utils/index.js @@ -1,10 +1,10 @@ const assert = require('assert'); module.exports = { - expectAssert: function (promise) { + expectAssert: async function (promise) { return expectEOSError(promise, 'eosio_assert_message_exception', 'assert'); }, - expectMissingAuthority: function (promise) { + expectMissingAuthority: async function (promise) { return expectEOSError(promise, 'missing_auth_exception', 'missing authority'); }, // Todo: Uncomment it once test cli command is ready @@ -20,12 +20,13 @@ module.exports = { // } } -let expectEOSError = async function (promise, errorType, errorInfo) { +const expectEOSError = async function (promise, errorType, errorInfo) { try { await promise; } catch (error) { - const expectedError = error.search(errorType) >= 0; - assert(expectedError, `Expected ${errorInfo}, got '${error}' instead`); + const message = error.message || error; + const expectedError = message.search(errorType) >= 0; + assert(expectedError, `Expected ${errorInfo}, got '${message}' instead`); return; } assert.fail(`Expected ${errorInfo} not received`); diff --git a/cli-commands/common/logger.js b/cli-commands/common/logger.js new file mode 100644 index 0000000..001726d --- /dev/null +++ b/cli-commands/common/logger.js @@ -0,0 +1,19 @@ +const chalk = require('chalk'); + +const logger = { + log: (message) => { + console.log(message); + }, + success: (message) => { + console.log(chalk.greenBright(message)); + }, + info: (message) => { + console.log(chalk.blueBright(message)); + }, + error: (message, error) => { + console.log(chalk.redBright(message)); + console.log(error); + } +} + +module.exports = logger; diff --git a/cli-commands/common/table.js b/cli-commands/common/table.js index b232557..7fc3d7e 100644 --- a/cli-commands/common/table.js +++ b/cli-commands/common/table.js @@ -1,8 +1,10 @@ const chalk = require('chalk').default; const CLITable = require('cli-table'); +const logger = require('./logger'); + class Table { - constructor(tableHead) { + constructor (tableHead) { this.table = new CLITable(tableHead); } @@ -25,7 +27,7 @@ class Table { } draw () { - console.log(this.table.toString()); + logger.log(this.table.toString()); } } diff --git a/cli-commands/helpers/async-soft-exec.js b/cli-commands/helpers/async-soft-exec.js index f1f44de..6176d9f 100644 --- a/cli-commands/helpers/async-soft-exec.js +++ b/cli-commands/helpers/async-soft-exec.js @@ -1,39 +1,21 @@ const exec = require('child_process').exec; class AsyncSoftExec { - constructor(command) { + constructor (command) { this.command = command; - - this.successCallback = () => { }; - this.errorCallback = (error) => { throw new Error(error) }; - } - - onError (callback) { - this.errorCallback = callback; - } - - onSuccess (callback) { - this.successCallback = callback; } async exec () { return new Promise((resolve, reject) => { exec(this.command, async (error, stdout) => { - try { - if (error) { - await this.errorCallback(error); - return void resolve(true); - } - - await this.successCallback(stdout); - return void resolve(true); - } catch (error) { - reject(error); + if (error) { + return void reject(error); } + + return void resolve(stdout); }); }); } } - module.exports = AsyncSoftExec; diff --git a/cli-commands/helpers/file-system-util.js b/cli-commands/helpers/file-system-util.js index 71d9c84..f468849 100644 --- a/cli-commands/helpers/file-system-util.js +++ b/cli-commands/helpers/file-system-util.js @@ -75,9 +75,6 @@ const fileSystemUtils = { }); }); }, - exists: (path) => { - return fs.existsSync(path); - }, readFile: (filePath) => { return fs.readFileSync(filePath); }, diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..cb77958 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,23 @@ +// Type definitions for EOSLime +// Project: https://github.com/LimeChain/eoslime +// Definitions by: Lyubomir Kiprov +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +import { utils } from './types/utils'; +import { ContractFactory } from './types/contract'; +import { Provider, NetworkDetails } from './types/provider'; +import { AccountFactory, MultiSignatureFactory } from './types/account'; + +interface EOSLime { + utils: utils; + Provider: Provider; + Account: AccountFactory; + Contract: ContractFactory; + MultiSigAccount: MultiSignatureFactory; +} + +export const utils: utils; +export const NETWORKS: Array; + +export function init (network?: string, config?: NetworkDetails): EOSLime; +export function init (config?: NetworkDetails): EOSLime; diff --git a/nyc.config.js b/nyc.config.js new file mode 100644 index 0000000..7feeb4d --- /dev/null +++ b/nyc.config.js @@ -0,0 +1,9 @@ +module.exports = { + "include": [ + "**/*.js" + ], + "exclude": [ + "tests/**/*.js", + "cli-commands/commands/test/specific/test-frameworks/mocha/index.js" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index b924d01..796ac67 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "eoslime", - "version": "1.0.4", + "version": "2.0.0", "description": "eoslime is an EOS development and deployment framework based on eosjs.js", "main": "index.js", + "types": "index.d.ts", "scripts": { - "test": "bash ./tests/testing-contracts/compile.sh && nyc --check-coverage mocha './tests/*.js'", - "test-dev": "bash ./tests/testing-contracts/compile.sh && mocha './tests/*.js'", + "build": "tsc", + "test": "nyc --check-coverage mocha './tests/**/*.js' && tsc&& bash ./tests/testing-contracts/compile.sh && mocha -r ts-node/register tests/**/*.ts", + "test-dev": "mocha './tests/**/*.js'&& tsc && bash ./tests/testing-contracts/compile.sh && mocha -r ts-node/register tests/**/*.ts", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov" }, "author": "Lyubomir Kiprov (Limechain)", @@ -16,11 +18,21 @@ "crypto-js": "3.1.9-1", "eosjs": "16.0.9", "is-invalid-path": "1.0.2", - "mocha": "5.2.0", "prompts": "2.2.1", "simple-git": "1.132.0", "yargs": "12.0.5" }, + "devDependencies": { + "@types/assert": "^1.5.1", + "@types/mocha": "^7.0.2", + "assert": "^2.0.0", + "mocha": "^5.2.0", + "nyc": "14.1.1", + "proxyquire": "^2.1.3", + "sinon": "^9.0.2", + "ts-node": "^8.10.2", + "typescript": "^3.9.5" + }, "keywords": [ "lime", "eos", @@ -40,8 +52,5 @@ "homepage": "https://github.com/LimeChain/eoslime#readme", "bin": { "eoslime": "./cli.js" - }, - "devDependencies": { - "nyc": "14.1.1" } } \ No newline at end of file diff --git a/src/account/authority-account/account.js b/src/account/authority-account/account.js deleted file mode 100644 index 2e7da6f..0000000 --- a/src/account/authority-account/account.js +++ /dev/null @@ -1,29 +0,0 @@ -const is = require('../../helpers/is') - -class AuthorityAccount { - - static construct(account, parentPermission) { - account.setAuthorityAbilities = setAuthorityAbilities(account, parentPermission); - return account; - } -} - -const setAuthorityAbilities = function (account, parentAuthority) { - return async function (abilities) { - is(abilities).instanceOf('Array'); - - await account.provider.eos.transaction(tr => { - for (let i = 0; i < abilities.length; i++) { - const ability = abilities[i]; - tr.linkauth({ - account: account.name, - code: ability.contract, - type: ability.action, - requirement: account.executiveAuthority.permission - }, { authorization: [`${this.name}@${parentAuthority}`] }); - } - }, { broadcast: true, sign: true, keyProvider: account.privateKey }); - } -} - -module.exports = AuthorityAccount; diff --git a/src/account/base-account.js b/src/account/base-account.js index dd4fb37..ca7c1ac 100644 --- a/src/account/base-account.js +++ b/src/account/base-account.js @@ -2,10 +2,10 @@ const eosECC = require('eosjs').modules.ecc; class BaseAccount { - constructor(name, privateKey, provider, permission) { + constructor (name, privateKey, provider, permission) { this.name = name; this.provider = provider; - this.executiveAuthority = { + this.authority = { actor: name, permission: permission } @@ -15,7 +15,7 @@ class BaseAccount { } async getAuthorityInfo () { - const authority = arguments[0] ? arguments[0] : this.executiveAuthority.permission; + const authority = arguments[0] ? arguments[0] : this.authority.permission; const accountInfo = await this.provider.eos.getAccount(this.name); const authorityInfo = accountInfo.permissions.find((permission) => { diff --git a/src/account/multi-signature-account/account.js b/src/account/multi-signature-account/account.js index a7a95bf..1e14040 100644 --- a/src/account/multi-signature-account/account.js +++ b/src/account/multi-signature-account/account.js @@ -3,37 +3,37 @@ const BaseAccount = require('../base-account'); class MultiSignatureAccount extends BaseAccount { - constructor(name, privateKey, provider, authority) { + constructor (name, privateKey, provider, authority) { super(name, privateKey, provider, authority) this.accounts = []; this.proposals = {}; } - loadKeys(privateKeys) { + loadKeys (privateKeys) { for (let i = 0; i < privateKeys.length; i++) { - this.accounts.push(new BaseAccount(this.name, privateKeys[i], this.provider, this.executiveAuthority.permission)); + this.accounts.push(new BaseAccount(this.name, privateKeys[i], this.provider, this.authority.permission)); } } - loadAccounts(accounts) { + loadAccounts (accounts) { for (let i = 0; i < accounts.length; i++) { is(accounts[i]).instanceOf('BaseAccount'); this.accounts.push(accounts[i]); } } - async propose(contractAction, actionData) { + async propose (contractAction, actionData) { is(contractAction).instanceOf('ContractFunction'); - const actionTx = await contractAction.sign(this, ...actionData); + const actionTx = await contractAction.sign(actionData, { from: this }); const proposalId = Date.now(); this.proposals[proposalId] = actionTx; return proposalId; } - async approve(publicKey, proposalId) { + async approve (publicKey, proposalId) { const approver = this.accounts.find((account) => { return account.publicKey == publicKey }); requireExistingApprover(approver); requireExistingProposal(this.proposals, proposalId); @@ -47,7 +47,7 @@ class MultiSignatureAccount extends BaseAccount { proposalTx.signatures.push(approverSignedTx.transaction.signatures[0]); } - async processProposal(proposalId) { + async processProposal (proposalId) { requireExistingProposal(this.proposals, proposalId); await requireEnoughApprovals(this, this.proposals[proposalId]); diff --git a/src/account/normal-account/account-factory.js b/src/account/normal-account/account-factory.js index 08416aa..86f42af 100644 --- a/src/account/normal-account/account-factory.js +++ b/src/account/normal-account/account-factory.js @@ -7,7 +7,7 @@ const utils = require('../../utils'); const DEFAULT_AUTHORITY = 'active'; class AccountFactory { - constructor(provider) { + constructor (provider) { this.provider = provider; } @@ -66,7 +66,7 @@ class AccountFactory { const dataToBeEncrypted = { name: newAccount.name, network: newAccount.provider.network, - authority: newAccount.executiveAuthority + authority: newAccount.authority }; const dataHash = crypto.hash(JSON.stringify({ ...dataToBeEncrypted, privateKey: newAccount.privateKey })); diff --git a/src/account/normal-account/account.js b/src/account/normal-account/account.js index 3e68a25..45bc617 100644 --- a/src/account/normal-account/account.js +++ b/src/account/normal-account/account.js @@ -1,11 +1,10 @@ const is = require('../../helpers/is') const eosECC = require('eosjs').modules.ecc; const BaseAccount = require('../base-account'); -const AuthorityAccount = require('../authority-account/account'); class Account extends BaseAccount { - constructor(name, privateKey, provider, permission) { + constructor (name, privateKey, provider, permission) { super(name, privateKey, provider, permission); } @@ -42,21 +41,49 @@ class Account extends BaseAccount { this.name, receiver.name, `${amount} ${symbol}`, - this.executiveAuthority, + this.authority, { broadcast: true, sign: true, keyProvider: this.privateKey } ); } - async createSubAuthority (authorityName, threshold = 1) { + async addAuthority (authorityName, threshold = 1) { const authorization = { threshold, keys: [{ key: this.publicKey, weight: threshold }] } - await updateAuthority.call(this, authorityName, this.executiveAuthority.permission, authorization); - const authorityAccount = new Account(this.name, this.privateKey, this.provider, authorityName); + return updateAuthority.call(this, authorityName, this.authority.permission, authorization); + } + + async setAuthorityAbilities (authorityName, abilities) { + is(abilities).instanceOf('Array'); - return AuthorityAccount.construct(authorityAccount, this.executiveAuthority.permission); + const accountInfo = await this.provider.eos.getAccount(this.name); + const hasAuthName = accountInfo.permissions.find((permissions) => { + return permissions.perm_name == authorityName; + }); + + if (!hasAuthName) { + throw new Error(` + Account does not have authority with name: [${authorityName}]. + You could add it by using [addAuthority] function. + For details check [Set authority abilities] suite in account-tests.js + `); + } + + const txReceipt = await this.provider.eos.transaction(tr => { + for (let i = 0; i < abilities.length; i++) { + const ability = abilities[i]; + tr.linkauth({ + account: this.name, + code: ability.contract, + type: ability.action, + requirement: authorityName + }, { authorization: [this.authority] }); + } + }, { broadcast: true, sign: true, keyProvider: this.privateKey }); + + return txReceipt; } async increaseThreshold (threshold) { @@ -82,7 +109,7 @@ class Account extends BaseAccount { } } - async addAuthorityKey (publicKey, weight = 1) { + async addOnBehalfKey (publicKey, weight = 1) { if (!eosECC.isValidPublic(publicKey)) { throw new Error('Provided public key is not a valid one'); } @@ -115,16 +142,18 @@ class Account extends BaseAccount { } const updateAuthority = async function (authorityName, parent, auth) { - await this.provider.eos.transaction(tr => { + const txReceipt = await this.provider.eos.transaction(tr => { tr.updateauth({ account: this.name, permission: authorityName, parent: parent, auth: auth - }, { authorization: [this.executiveAuthority] }); + }, { authorization: [this.authority] }); }, { broadcast: true, sign: true, keyProvider: this.privateKey }); + return txReceipt; + } module.exports = Account; diff --git a/src/contract/contract-factory.js b/src/contract/contract-factory.js index 7d2b71a..d087c1b 100644 --- a/src/contract/contract-factory.js +++ b/src/contract/contract-factory.js @@ -22,7 +22,7 @@ const EVENTS = { class ContractFactory extends ContractInitializator { - constructor(provider) { + constructor (provider) { super(provider); Object.assign(this.events, EVENTS); } @@ -77,7 +77,7 @@ class ContractFactory extends ContractInitializator { options = Object.assign(defaultDeployOptions, options); await executeOptions(contract, options); - this.emit(EVENTS.deploy, [setCodeTxReceipt, setAbiTxReceipt], contract); + this.emit(EVENTS.deploy, contract, [setCodeTxReceipt, setAbiTxReceipt]); return contract; } diff --git a/src/contract/contract-function/contract-function.js b/src/contract/contract-function/contract-function.js index d2571a7..c71e56f 100644 --- a/src/contract/contract-function/contract-function.js +++ b/src/contract/contract-function/contract-function.js @@ -8,102 +8,105 @@ const EVENTS = { class ContractFunction extends EventClass { - constructor(contract, functionName, functionFields) { + constructor (contract, functionName, functionFields) { super(EVENTS); + this.contract = contract; this.functionName = functionName; this.functionFields = functionFields; - this.isTransactional = true; } - async broadcast(...params) { + async broadcast (params, options) { is(this.contract.executor).instanceOf('BaseAccount', 'executor is missing'); - const functionParamsCount = this.functionFields.length; - const functionParams = params.slice(0, functionParamsCount); - const functionRawTxData = buildFunctionRawTxData.call(this, this.contract.executor, functionParams); - - // Optionals starts from the last function parameter position - const optionals = params[functionParamsCount] instanceof Object ? params[functionParamsCount] : null; - for (let i = 0; i < optionalsFunctions.all.length; i++) { - const optionalFunction = optionalsFunctions.all[i]; - optionalFunction(optionals, functionRawTxData); + const txOptions = { + broadcast: true, + sign: true } - const txReceipt = await executeFunction( - this.contract.provider.eos, - functionRawTxData, - { broadcast: true, sign: true, keyProvider: functionRawTxData.defaultExecutor.privateKey } - ); + const txReceipt = await executeFunction.call(this, params, options, txOptions); - this.emit(EVENTS.processed, txReceipt, functionParams); + this.emit(EVENTS.processed, txReceipt, params); return txReceipt; } - async getRawTransaction(...params) { - const functionRawTxData = buildFunctionRawTxData.call(this, this.contract.executor, params); - const rawTransaction = await executeFunction( - this.contract.provider.eos, - functionRawTxData, - { broadcast: false, sign: false } - ); + async getRawTransaction (params, options) { + const txOptions = { + broadcast: false, + sign: false + } + const rawTransaction = await executeFunction.call(this, params, options, txOptions); return rawTransaction.transaction.transaction; } - async sign(signer, ...params) { - is(signer).instanceOf('BaseAccount'); - - const functionRawTxData = buildFunctionRawTxData.call(this, signer, params); - const rawTransaction = await executeFunction( - this.contract.provider.eos, - functionRawTxData, - { broadcast: false, sign: true, keyProvider: signer.privateKey } - ); + async sign (params, options) { + const txOptions = { + broadcast: false, + sign: true + } + const rawTransaction = await executeFunction.call(this, params, options, txOptions); return rawTransaction.transaction; } } -const buildFunctionRawTxData = function (authorizer, params) { +async function executeFunction (params, fnOptions, txOptions) { + const functionRawTxData = buildFunctionRawTxData.call( + this, + this.contract.executor, + params, + fnOptions + ); + + return this.contract.provider.eos.transaction( + { + actions: functionRawTxData.actions + }, + { ...txOptions, keyProvider: functionRawTxData.defaultExecutor.privateKey } + ); +}; + +function buildFunctionRawTxData (authorizer, params, options) { const structuredParams = structureParamsToExpectedLook(params, this.functionFields); const functionTx = buildMainFunctionTx(this.contract.name, this.functionName, structuredParams, authorizer); + processOptions(options, functionTx); + return functionTx; } -const buildMainFunctionTx = function (contractName, actionName, data, authorizationAccount) { +function structureParamsToExpectedLook (params, expectedParamsLook) { + let structuredParams = {}; + + for (let i = 0; i < expectedParamsLook.length; i++) { + let expectedParam = expectedParamsLook[i].name; + structuredParams[expectedParam] = params[i]; + } + + return structuredParams; +}; + +function buildMainFunctionTx (contractName, actionName, data, authorizationAccount) { return { defaultExecutor: authorizationAccount, actions: [ { account: contractName, name: actionName, - authorization: [authorizationAccount.executiveAuthority], + authorization: [authorizationAccount.authority], data: data } ] }; }; -const structureParamsToExpectedLook = function (params, expectedParamsLook) { - let structuredParams = {}; - - for (let i = 0; i < expectedParamsLook.length; i++) { - let expectedParam = expectedParamsLook[i].name; - structuredParams[expectedParam] = params[i]; +function processOptions (options, functionRawTxData) { + const optionals = options instanceof Object ? options : null; + for (let i = 0; i < optionalsFunctions.all.length; i++) { + const optionalFunction = optionalsFunctions.all[i]; + optionalFunction(optionals, functionRawTxData); } - - return structuredParams; -}; - -const executeFunction = async function (eos, functionRawTx, options) { - return eos.transaction( - { - actions: functionRawTx.actions - }, - options - ); -}; +} module.exports = ContractFunction; diff --git a/src/contract/contract-function/function-optionals/from.js b/src/contract/contract-function/function-optionals/from.js index 44de550..4009bd7 100644 --- a/src/contract/contract-function/function-optionals/from.js +++ b/src/contract/contract-function/function-optionals/from.js @@ -1,10 +1,10 @@ -const Account = require("./../../../account/normal-account/account"); +const is = require('../../../helpers/is') const fromOption = function (optionals, rawTransaction) { - if (optionals && optionals.from instanceof Account) { + if (optionals && optionals.from && is(optionals.from).instanceOf('BaseAccount')) { rawTransaction.defaultExecutor = optionals.from; for (let i = 0; i < rawTransaction.actions.length; i++) { - rawTransaction.actions[i].authorization = [rawTransaction.defaultExecutor.executiveAuthority]; + rawTransaction.actions[i].authorization = [rawTransaction.defaultExecutor.authority]; } } }; diff --git a/src/contract/contract-function/function-optionals/tokens.js b/src/contract/contract-function/function-optionals/tokens.js index ca3eeda..6d74287 100644 --- a/src/contract/contract-function/function-optionals/tokens.js +++ b/src/contract/contract-function/function-optionals/tokens.js @@ -1,10 +1,9 @@ const tokensOptional = function (optionals, rawTransaction) { if (optionals && optionals.tokens) { - rawTransaction.actions.push({ account: "eosio.token", name: "transfer", - authorization: [rawTransaction.defaultExecutor.executiveAuthority], + authorization: [rawTransaction.defaultExecutor.authority], data: { from: rawTransaction.defaultExecutor.name, to: rawTransaction.actions[0].account, diff --git a/src/contract/contract-function/function-optionals/unique.js b/src/contract/contract-function/function-optionals/unique.js index e8efff4..fee30ee 100644 --- a/src/contract/contract-function/function-optionals/unique.js +++ b/src/contract/contract-function/function-optionals/unique.js @@ -3,7 +3,7 @@ const uniqueOptional = function (optionals, rawTransaction) { rawTransaction.actions.push({ account: "eosio.null", name: "nonce", - authorization: [rawTransaction.defaultExecutor.executiveAuthority], + authorization: [rawTransaction.defaultExecutor.authority], data: { value: `${Date.now()}` } diff --git a/src/contract/contract-function/functions-factory.js b/src/contract/contract-function/functions-factory.js index 461413c..695a7a6 100644 --- a/src/contract/contract-function/functions-factory.js +++ b/src/contract/contract-function/functions-factory.js @@ -2,7 +2,7 @@ const ContractFunction = require('./contract-function'); class FunctionsFactory { - static createFunction(contract, functionName, functionFields) { + static createFunction (contract, functionName, functionFields) { const contractFunction = new ContractFunction(contract, functionName, functionFields); const proxyHandler = { diff --git a/src/contract/contract-initializator.js b/src/contract/contract-initializator.js index ab2eedb..3892e09 100644 --- a/src/contract/contract-initializator.js +++ b/src/contract/contract-initializator.js @@ -10,14 +10,12 @@ const EVENTS = { class ContractInitializator extends EventClass { - constructor(provider) { + constructor (provider) { super(EVENTS); this.provider = provider; } fromFile (abi, contractName, contractExecutorAccount = this.provider.defaultAccount) { - is(contractExecutorAccount).instanceOf('BaseAccount'); - let abiInterface = abi; if (contractFilesReader.doesAbiExists(abi)) { abiInterface = contractFilesReader.readABIFromFile(abi); @@ -30,8 +28,6 @@ class ContractInitializator extends EventClass { } async at (contractName, contractExecutorAccount = this.provider.defaultAccount) { - is(contractExecutorAccount).instanceOf('BaseAccount'); - const abiInterface = (await this.provider.eos.getAbi(contractName)).abi; const contract = new Contract(this.provider, abiInterface, contractName, contractExecutorAccount); diff --git a/src/contract/contract.js b/src/contract/contract.js index b3e5681..acb5591 100644 --- a/src/contract/contract.js +++ b/src/contract/contract.js @@ -8,6 +8,9 @@ class Contract { this.provider = provider; this.executor = contractExecutorAccount; + this.actions = {}; + this.tables = {}; + declareFunctionsFromABI.call(this, abi); declareTableGetters.call(this, abi); } @@ -37,7 +40,7 @@ let declareFunctionsFromABI = function (abi) { for (let i = 0; i < contractActions.length; i++) { const functionName = contractActions[i].name; const functionType = contractActions[i].type; - this[functionName] = FunctionsFactory.createFunction(this, functionName, contractStructs[functionType].fields) + this.actions[functionName] = FunctionsFactory.createFunction(this, functionName, contractStructs[functionType].fields) } }; @@ -46,7 +49,7 @@ let declareTableGetters = function (abi) { for (let i = 0; i < contractTables.length; i++) { const tableName = contractTables[i].name; - this[tableName] = new Proxy({}, { + this.tables[tableName] = new Proxy({}, { get: (target, name) => { return this.provider.select(tableName).from(this.name)[name]; } diff --git a/src/helpers/crypto.js b/src/helpers/crypto.js index 1396330..e6f1075 100644 --- a/src/helpers/crypto.js +++ b/src/helpers/crypto.js @@ -6,7 +6,7 @@ module.exports = { let dataHash = cryptoJS.SHA256(data).toString(cryptoJS.enc.Hex); return dataHash; } catch (error) { - throw new Error('Couldn\'t hash the data') + throw new Error('Couldn\'t hash the data'); } }, encrypt: function (data, password) { diff --git a/src/helpers/is.js b/src/helpers/is.js index 8a43457..313378f 100644 --- a/src/helpers/is.js +++ b/src/helpers/is.js @@ -6,7 +6,7 @@ module.exports = function (data) { throw new Error(errorMessage); } - recursivelyCheckIfInstance(data, ofObject); + return recursivelyCheckIfInstance(data, ofObject); } } } diff --git a/src/network-providers/jungle3-provider.js b/src/network-providers/jungle3-provider.js new file mode 100644 index 0000000..6bb0f31 --- /dev/null +++ b/src/network-providers/jungle3-provider.js @@ -0,0 +1,15 @@ +const BaseProvider = require('./base-provider'); + +const Jungle3NetworkConfig = { + name: 'jungle3', + url: 'https://jungle3.cryptolions.io', + chainId: '2a02a0053e5a8cf73a56ba0fda11e4d92e0238a4a2aa74fccf46d5a910746840' +} + +class Jungle3Provider extends BaseProvider { + constructor(networkConfig) { + super(Object.assign({}, Jungle3NetworkConfig, networkConfig)) + } +} + +module.exports = Jungle3Provider diff --git a/src/network-providers/kylin-provider.js b/src/network-providers/kylin-provider.js index 9c99cda..7ef9497 100644 --- a/src/network-providers/kylin-provider.js +++ b/src/network-providers/kylin-provider.js @@ -3,7 +3,7 @@ const BaseProvider = require('./base-provider'); const KylinNetworkConfig = { name: 'kylin', - url: 'https://kylin.eoscanada.com', + url: 'https://api.kylin.alohaeos.com', chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191' } diff --git a/src/network-providers/provider-factory.js b/src/network-providers/provider-factory.js index 418095c..171d031 100644 --- a/src/network-providers/provider-factory.js +++ b/src/network-providers/provider-factory.js @@ -5,6 +5,7 @@ const MainProvider = require('./main-provider'); const KylinProvider = require('./kylin-provider'); const LocalProvider = require('./local-provider'); const JungleProvider = require('./jungle-provider'); +const Jungle3Provider = require('./jungle3-provider'); const WorbliProvider = require('./worbli-provider'); const CustomProvider = require('./custom-provider'); @@ -14,6 +15,7 @@ const PROVIDERS = { kylin: (networkConfig) => { return new KylinProvider(networkConfig) }, local: (networkConfig) => { return new LocalProvider(networkConfig) }, jungle: (networkConfig) => { return new JungleProvider(networkConfig) }, + jungle3: (networkConfig) => { return new Jungle3Provider(networkConfig) }, worbli: (networkConfig) => { return new WorbliProvider(networkConfig) }, custom: (networkConfig) => { return new CustomProvider(networkConfig) } } diff --git a/tests/account-tests.js b/tests/account-tests.js index c798241..f9eb713 100644 --- a/tests/account-tests.js +++ b/tests/account-tests.js @@ -37,7 +37,7 @@ const Networks = { }, kylin: { name: 'kylin', - url: 'https://kylin.eoscanada.com', + url: 'https://api.kylin.alohaeos.com', chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191' }, custom: { @@ -72,8 +72,8 @@ describe('Account', function () { try { const tokenAccount = await Account.createFromName('eosio.token'); const tokenContract = await eoslimeTool.Contract.deployOnAccount(TOKEN_WASM_PATH, TOKEN_ABI_PATH, tokenAccount); - await tokenContract.create(tokenAccount.name, TOTAL_SUPPLY); - await tokenContract.issue(ACCOUNT_NAME, TOTAL_SUPPLY, 'memo'); + await tokenContract.actions.create([tokenAccount.name, TOTAL_SUPPLY]); + await tokenContract.actions.issue([ACCOUNT_NAME, TOTAL_SUPPLY, 'memo']); } catch (error) { } } @@ -86,8 +86,8 @@ describe('Account', function () { assert(account.name == ACCOUNT_NAME, 'Incorrect name'); assert(account.privateKey == ACCOUNT_PRIVATE_KEY, 'Incorrect private key'); assert(account.publicKey == ACCOUNT_PUBLIC_KEY, 'Incorrect public key'); - assert(account.executiveAuthority.actor == EXECUTIVE_AUTHORITY.actor, 'Incorrect executive authority actor'); - assert(account.executiveAuthority.permission == EXECUTIVE_AUTHORITY.permission, 'Incorrect executive authority permission'); + assert(account.authority.actor == EXECUTIVE_AUTHORITY.actor, 'Incorrect executive authority actor'); + assert(account.authority.permission == EXECUTIVE_AUTHORITY.permission, 'Incorrect executive authority permission'); const network = account.provider.network; assert(JSON.stringify(account.provider.network) == JSON.stringify(Networks[network.name])) @@ -237,20 +237,22 @@ describe('Account', function () { }); }); - describe('Create authority', function () { + describe('Add authority', function () { const AUTHORITY = 'contracts'; const PARENT_AUTHORITY = 'active'; - it('Should create authority', async () => { + it('Should add authority', async () => { let account = await Account.createRandom(); let authority = await getAuthorityForAccount(AUTHORITY, account.name); assert(authority == undefined); - const newAuthorityAccount = await account.createSubAuthority(AUTHORITY); + await account.addAuthority(AUTHORITY); + + const newAuthorityAccount = Account.load(account.name, account.privateKey, AUTHORITY); assert(newAuthorityAccount.name == account.name); - assert(newAuthorityAccount.executiveAuthority.actor == newAuthorityAccount.name); - assert(newAuthorityAccount.executiveAuthority.permission == AUTHORITY); + assert(newAuthorityAccount.authority.actor == newAuthorityAccount.name); + assert(newAuthorityAccount.authority.permission == AUTHORITY); authority = await getAuthorityForAccount(AUTHORITY, newAuthorityAccount.name); assert(authority.parent == PARENT_AUTHORITY); @@ -288,7 +290,7 @@ describe('Account', function () { it('Should throw if one try to create a permission for non-existing authority', async () => { try { let account = await Account.createRandom(); - account.executiveAuthority.permission = 'FAKE'; + account.authority.permission = 'FAKE'; await account.addPermission(PERMISSION); @@ -321,7 +323,7 @@ describe('Account', function () { assert(authorityInfo.required_auth.keys.length == 1); const keysPair = await eoslime.utils.generateKeys(); - await account.addAuthorityKey(keysPair.publicKey); + await account.addOnBehalfKey(keysPair.publicKey); authorityInfo = await account.getAuthorityInfo(); assert(authorityInfo.required_auth.keys.find((keyData) => { return keyData.key == keysPair.publicKey })); @@ -336,7 +338,7 @@ describe('Account', function () { assert(authorityInfo.required_auth.keys[0].weight == 1); const keysPair = await eoslime.utils.generateKeys(); - await account.addAuthorityKey(keysPair.publicKey, WEIGHT); + await account.addOnBehalfKey(keysPair.publicKey, WEIGHT); authorityInfo = await account.getAuthorityInfo(); assert(authorityInfo.required_auth.keys.find((key) => { return key.weight == WEIGHT })); @@ -349,8 +351,8 @@ describe('Account', function () { assert(authorityInfo.required_auth.keys.length == 1); const keysPair = await eoslime.utils.generateKeys(); - await account.addAuthorityKey(keysPair.publicKey); - await account.addAuthorityKey(keysPair.publicKey); + await account.addOnBehalfKey(keysPair.publicKey); + await account.addOnBehalfKey(keysPair.publicKey); authorityInfo = await account.getAuthorityInfo(); @@ -361,7 +363,7 @@ describe('Account', function () { it('Should throw if one provide an invalid public key', async () => { try { const account = await Account.createRandom(); - await account.addAuthorityKey('Invalid public key'); + await account.addOnBehalfKey('Invalid public key'); assert(false); } catch (error) { @@ -428,7 +430,7 @@ describe('Account', function () { assert(authorityInfo.required_auth.threshold == 1); const keysPair = await eoslime.utils.generateKeys(); - await account.addAuthorityKey(keysPair.publicKey); + await account.addOnBehalfKey(keysPair.publicKey); await account.increaseThreshold(THRESHOLD); authorityInfo = await account.getAuthorityInfo(); @@ -481,36 +483,62 @@ describe('Account', function () { const faucetContract = await eoslimeTool.Contract.deploy(FAUCET_WASM_PATH, FAUCET_ABI_PATH); const account = await eoslimeTool.Account.createRandom(); - const accountRandomAuth = await account.createSubAuthority('random'); + await account.addAuthority('random'); + const accountRandomAuth = eoslimeTool.Account.load(account.name, account.privateKey, 'random'); try { - await faucetContract.produce(account.name, "100.0000 TKNS", account.name, "memo", { from: accountRandomAuth }); + await faucetContract.actions.produce( + [account.name, "100.0000 TKNS", account.name, "memo"], + { from: accountRandomAuth } + ); } catch (error) { assert(error.includes('action declares irrelevant authority')); } - await accountRandomAuth.setAuthorityAbilities([ + await account.setAuthorityAbilities('random', [ { action: 'produce', contract: faucetContract.name } ]); - await faucetContract.produce(account.name, "100.0000 TKNS", account.name, "memo", { from: accountRandomAuth }); + await faucetContract.actions.produce( + [account.name, "100.0000 TKNS", account.name, "memo"], + { from: accountRandomAuth } + ); }); it('Should throw if one does not provide array as abilities', async () => { try { const account = await eoslimeTool.Account.createRandom(); - const authorityAccount = await account.createSubAuthority('random'); - - await authorityAccount.setAuthorityAbilities('Fake ability'); + await account.addAuthority('random'); + await account.setAuthorityAbilities('random', 'Fake ability'); assert(false); } catch (error) { assert(error.message.includes('Provided String is not an instance of Array')); } }); + + it('Should throw if one does not provide existing authority', async () => { + try { + const account = await eoslimeTool.Account.createRandom(); + + // Random does not exists + await account.setAuthorityAbilities('random', [ + { + action: 'test', + contract: 'eosio' + } + ]); + + assert(false); + } catch (error) { + assert(error.message.includes('Account does not have authority with name: [random].')); + assert(error.message.includes('You could add it by using [addAuthority] function.')); + assert(error.message.includes('For details check [Set authority abilities] suite in account-tests.js')); + } + }); }); const getAuthorityForAccount = async function (authorityName, accountName) { @@ -715,8 +743,8 @@ describe('Account', function () { assert(decryptedJSONAccount.name, 'Incorrect name'); assert(decryptedJSONAccount.privateKey, 'Incorrect private key'); assert(decryptedJSONAccount.publicKey, 'Incorrect public key'); - assert(decryptedJSONAccount.executiveAuthority.actor == decryptedJSONAccount.name, 'Incorrect authority actor'); - assert(decryptedJSONAccount.executiveAuthority.permission == 'active', 'Incorrect authority permission'); + assert(decryptedJSONAccount.authority.actor == decryptedJSONAccount.name, 'Incorrect authority actor'); + assert(decryptedJSONAccount.authority.permission == 'active', 'Incorrect authority permission'); assert(JSON.stringify(decryptedJSONAccount.provider.network) == JSON.stringify(Networks['local']), 'Incorrect network'); }); diff --git a/tests/cli-commands/compile-tests.js b/tests/cli-commands/compile-tests.js new file mode 100644 index 0000000..6189abf --- /dev/null +++ b/tests/cli-commands/compile-tests.js @@ -0,0 +1,203 @@ +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const definition = require('../../cli-commands/commands/compile/definition'); + +const Command = require('../../cli-commands/commands/command'); +const CompileCommand = require('../../cli-commands/commands/compile/index'); + +const Logger = require('./utils/logger'); +const logger = new Logger(); + +describe('Compile Command', function () { + + beforeEach(async () => { + logger.hide(sinon); + }); + + afterEach(async () => { + sinon.restore(); + }); + + describe('Command', function () { + + it('Should initialize command properly', async () => { + const compileCommand = new CompileCommand(); + assert(compileCommand instanceof Command); + assert(compileCommand.template == definition.template); + assert(compileCommand.description = definition.description); + assert(compileCommand.options == definition.options); + assert(compileCommand.subcommands.length == 0); + }); + + function stubBaseCommand (cb) { + return { + '../command': class FakeBaseCommand { + processOptions () { + return cb(); + } + } + } + } + + function stubAsyncSoftExec (checkPoints) { + return { + '../../helpers/async-soft-exec': class FakeAsyncSoftExec { + exec () { checkPoints.exec--; } + } + } + } + + it('Should compile ', async () => { + const checkPoints = { + exec: 1, + createDir: 1 + } + + const compileCommand = new (proxyquire( + '../../cli-commands/commands/compile/index', + { + ...stubBaseCommand(() => { + return { path: [{ fileName: 'test', fullPath: './test.cpp' }] } + }), + ...stubAsyncSoftExec(checkPoints), + '../../helpers/file-system-util': { + createDir: () => { + checkPoints.createDir--; + } + } + } + )); + + await compileCommand.execute(); + + assert(checkPoints.exec == 0); + assert(checkPoints.createDir == 0); + }); + + it('Should log in case any contracts was found', async () => { + const compileCommand = new (proxyquire('../../cli-commands/commands/compile/index', + { ...stubBaseCommand(() => { return { path: [] } }) } + )); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('info', (message) => { + if (message.includes('There is not a contract to compile')) { + return resolve(true); + } + }); + + await compileCommand.execute({ 'path': './' }); + }); + + await waitToPass; + }); + + it('Should log in case a contract could not be compiled', async () => { + const compileCommand = new (proxyquire( + '../../cli-commands/commands/compile/index', + { + ...stubBaseCommand(() => { + return { path: [{ fileName: 'test', fullPath: './test.cpp' }] } + }), + '../../helpers/async-soft-exec': class FakeAsyncSoftExec { + exec () { throw new Error('Fake error'); } + }, + '../../helpers/file-system-util': { + createDir: () => { } + } + } + )); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Unsuccessful compilation of')) { + return resolve(true); + } + }); + + await compileCommand.execute({ 'path': './' }); + }); + + await waitToPass; + }); + + it('Should log in case of an error', async () => { + const compileCommand = new (proxyquire('../../cli-commands/commands/compile/index', + { ...stubBaseCommand(() => { throw new Error('Fake Error') }) } + )); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Unsuccessful compilation')) { + return resolve(true); + } + }); + + await compileCommand.execute({ 'path': './' }); + }); + + await waitToPass; + }); + }); + + + describe('Options', function () { + describe('Path', function () { + + function stubFileSystemUtils (isFolder) { + const Option = proxyquire( + '../../cli-commands/commands/compile/options/path-option', + { + '../../../helpers/file-system-util': { + isDir: () => { + return isFolder; + }, + recursivelyReadDir: () => { + return [ + { + fileName: 'test1.cpp', + fullPath: `./custom/test1.cpp` + }, { + fileName: 'test2.cpp', + fullPath: `./custom/test2.cpp` + } + ] + } + } + } + ); + + return Option; + } + + it('Should return contract path', async () => { + const pathOption = stubFileSystemUtils(false); + const result = await pathOption.process('./test.cpp'); + + assert(result.length == 1); + assert(result[0].fileName == 'test'); + assert(result[0].fullPath == './test.cpp'); + }); + + it('Should return contracts paths from a folder', async () => { + const pathOption = stubFileSystemUtils(true); + const result = await pathOption.process('./test.cpp'); + + assert(result.length == 2); + assert(result[0].fileName == 'test1'); + assert(result[1].fileName == 'test2'); + assert(result[0].fullPath == './custom/test1.cpp'); + assert(result[1].fullPath == './custom/test2.cpp'); + }); + + it('Should return empty array if any contracts was found', async () => { + const pathOption = stubFileSystemUtils(false); + const result = await pathOption.process('./test.fake'); + + assert(result.length == 0); + }); + }); + }); +}); diff --git a/tests/cli-commands/deploy-tests.js b/tests/cli-commands/deploy-tests.js new file mode 100644 index 0000000..134873a --- /dev/null +++ b/tests/cli-commands/deploy-tests.js @@ -0,0 +1,199 @@ +const path = require('path') +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const definition = require('../../cli-commands/commands/deploy/definition'); + +const Command = require('../../cli-commands/commands/command'); +const DeployCommand = require('../../cli-commands/commands/deploy/index'); + +const NetworkOption = require('../../cli-commands/commands/deploy/options/network-option'); + +const Logger = require('./utils/logger'); +const logger = new Logger(); + +describe('Deploy Command', function () { + + let deployCommand; + + beforeEach(async () => { + logger.hide(sinon); + deployCommand = new DeployCommand(); + }); + + afterEach(async () => { + sinon.restore(); + }); + + describe('Command', function () { + + it('Should initialize command properly', async () => { + assert(deployCommand instanceof Command); + assert(deployCommand.template == definition.template); + assert(deployCommand.description = definition.description); + assert(deployCommand.options == definition.options); + assert(deployCommand.subcommands.length == 0); + }); + + function stubBaseCommand (cb) { + return { + '../command': class FakeBaseCommand { + processOptions (args) { + return cb(args); + } + } + } + } + + it('Should deploy contracts', async () => { + const deployCommand = new (proxyquire('../../cli-commands/commands/deploy/index', { + ...stubBaseCommand(() => { + return { + path: [{ + deploy: (network, deployer) => { + assert(network == 'custom'); + assert(deployer == 'deployer'); + } + }], + network: 'custom', + deployer: 'deployer' + } + }) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('success', (message) => { + if (message.includes('Successful deployment of')) { + return resolve(true); + } + }); + + await deployCommand.execute(); + }); + + await waitToPass; + }); + + it('Should log error message in case a deployment script throws', async () => { + const deployCommand = new (proxyquire('../../cli-commands/commands/deploy/index', { + ...stubBaseCommand(() => { + return { + path: [{ + deploy: () => { + throw new Error('Fake Error'); + } + }] + } + }) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Unsuccessful deployment of')) { + return resolve(true); + } + }); + + await deployCommand.execute(); + }); + + await waitToPass; + }); + + it('Should log error message in case of error', async () => { + const deployCommand = new (proxyquire('../../cli-commands/commands/deploy/index', { + ...stubBaseCommand(() => { throw new Error('Fake error'); }) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes(' Unsuccessful deployment')) { + return resolve(true); + } + }); + + await deployCommand.execute(); + }); + + await waitToPass; + }); + }); + + + describe('Options', function () { + + describe('Path', function () { + + function stubFileSystemUtils (isFolder) { + return { + '../../../helpers/file-system-util': { + isDir: () => { return isFolder; }, + isFile: () => { return !isFolder; }, + recursivelyReadDir: (dir) => { + return [{ fileName: 'fileName', fullPath: `${dir}/fullPath` }]; + } + } + } + } + + function stubDeploymentPath (deploymentPath) { + return { + [deploymentPath]: 'deploymentPath' + } + } + + it('Should return a deployment script', async () => { + const pathOption = proxyquire('../../cli-commands/commands/deploy/options/path-option', { + ...stubFileSystemUtils(false), + ...stubDeploymentPath(path.resolve('./', './custom.js')) + }); + + const deploymentScript = (await pathOption.process('./custom.js'))[0]; + assert(deploymentScript.fileName == './custom.js'); + assert(deploymentScript.deploy == 'deploymentPath'); + }); + + it('Should return all deployment scripts from folder', async () => { + const pathOption = proxyquire('../../cli-commands/commands/deploy/options/path-option', { + ...stubFileSystemUtils(true), + ...stubDeploymentPath(path.resolve('./', './custom/fullPath')) + }); + + const deploymentScripts = await pathOption.process('./custom'); + assert(deploymentScripts[0].fileName == 'fileName'); + assert(deploymentScripts[0].deploy == 'deploymentPath'); + }); + }); + + describe('Network', function () { + + it('Should return an instance of eoslime', async () => { + const eoslimeInstance = NetworkOption.process('local'); + assert(eoslimeInstance.Provider.network.name == 'local'); + }); + }); + + describe('Deployer', function () { + + function stubPrompts (output) { + return { + 'prompts': () => { return { value: output } } + } + } + + it('Should return an instance of eoslime.Account', async () => { + const deployerOption = proxyquire('../../cli-commands/commands/deploy/options/deployer-option', { + ...stubPrompts('5KieRy975NgHk5XQfn8r6o3pcqJDF2vpeV9bDiuB5uF4xKCTwRF@active') + }); + + const deployer = await deployerOption.process('name', { network: 'local' }); + + assert(deployer.constructor.name == 'Account'); + assert(deployer.name == 'name'); + assert(deployer.privateKey == '5KieRy975NgHk5XQfn8r6o3pcqJDF2vpeV9bDiuB5uF4xKCTwRF'); + assert(deployer.authority.permission == 'active'); + }); + }); + }); +}); diff --git a/tests/cli-commands/init-tests.js b/tests/cli-commands/init-tests.js new file mode 100644 index 0000000..2e88182 --- /dev/null +++ b/tests/cli-commands/init-tests.js @@ -0,0 +1,160 @@ +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const definition = require('../../cli-commands/commands/init/definition'); + +const Command = require('../../cli-commands/commands/command'); +const InitCommand = require('../../cli-commands/commands/init/index'); + +const Logger = require('./utils/logger'); +const logger = new Logger(); + +describe('Init Command', function () { + + beforeEach(async () => { + logger.hide(sinon); + }); + + afterEach(async () => { + sinon.restore(); + }); + + describe('Command', function () { + + it('Should initialize command properly', async () => { + const initCommand = new InitCommand(); + assert(initCommand instanceof Command); + assert(initCommand.template == definition.template); + assert(initCommand.description = definition.description); + assert(initCommand.options == definition.options); + assert(initCommand.subcommands.length == 0); + }); + + function stubBaseCommand (cb) { + return { + '../command': class FakeBaseCommand { + processOptions (args) { + return cb(args); + } + } + } + } + + function stubFileSystemUtils (checkPoints) { + return { + '../../helpers/file-system-util': { + createDir: () => { + checkPoints.createDir--; + }, + copyFileFromTo: () => { + checkPoints.copyFileFromTo--; + } + } + } + } + + function stubAsyncSoftExec (checkPoints) { + return { + '../../helpers/async-soft-exec': class FakeAsyncSoftExec { + exec () { checkPoints.exec--; } + } + } + } + + it('Should init project structure', async () => { + const checkPoints = { + exec: 1, + createDir: 3, + copyFileFromTo: 1, + } + + const initCommand = new (proxyquire('../../cli-commands/commands/init/index', { + ...stubAsyncSoftExec(checkPoints), + ...stubFileSystemUtils(checkPoints), + ...stubBaseCommand((args) => { + return new Promise((resolve) => { + assert(args['with-example'] == false); + resolve(true); + }); + }) + })); + + await initCommand.execute({ 'with-example': false }); + + assert(checkPoints.exec == 0); + assert(checkPoints.createDir == 0); + assert(checkPoints.copyFileFromTo == 0); + }); + + it('Should log error message in case an option throws', async () => { + const initCommand = new (proxyquire('../../cli-commands/commands/init/index', { + ...stubAsyncSoftExec(), + ...stubFileSystemUtils(), + ...stubBaseCommand(() => { throw new Error('Fake error'); }) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Unsuccessful installation')) { + return resolve(true); + } + }); + + await initCommand.execute({ 'with-example': true }); + }); + + await waitToPass; + }); + }); + + describe('Options', function () { + describe('With-example', function () { + + const checkPoints = { + createDir: 1, + copyFileFromTo: 2, + copyAllFilesFromDirTo: 1 + } + + function stubFileSystemUtils () { + const Option = proxyquire( + '../../cli-commands/commands/init/options/with-example/with-example-option', + { + '../../../../helpers/file-system-util': { + createDir: () => { + checkPoints.createDir--; + }, + copyFileFromTo: () => { + checkPoints.copyFileFromTo--; + }, + copyAllFilesFromDirTo: () => { + checkPoints.copyAllFilesFromDirTo--; + } + } + } + ); + + return Option; + } + + it('Should init project structure [with-example = false]', async () => { + const checkPointUntouched = JSON.stringify(checkPoints); + + const exampleOption = stubFileSystemUtils(); + exampleOption.process(false); + + assert(checkPointUntouched == JSON.stringify(checkPoints)); + }); + + it('Should init project structure and provide example files', async () => { + const exampleOption = stubFileSystemUtils(); + exampleOption.process(true); + + assert(checkPoints.createDir == 0); + assert(checkPoints.copyFileFromTo == 0); + assert(checkPoints.copyAllFilesFromDirTo == 0); + }); + }); + }); +}); diff --git a/tests/cli-commands/miscellaneous.js b/tests/cli-commands/miscellaneous.js new file mode 100644 index 0000000..bf0e012 --- /dev/null +++ b/tests/cli-commands/miscellaneous.js @@ -0,0 +1,397 @@ +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const Command = require('../../cli-commands/commands/command'); +const GroupCommand = require('../../cli-commands/commands/group-command'); + +// Commons +const logger = require('../../cli-commands/common/logger'); +const Table = require('../../cli-commands/common/table'); + +const Logger = require('./utils/logger'); +const testLogger = new Logger(); + +describe('Miscellaneous', function () { + + describe('CLI Command', function () { + it('Should process command options', async () => { + const commandOptions = [{ name: 'test', process: (args) => { return args; } }]; + const command = new Command({ options: commandOptions }); + + const result = await command.processOptions({ 'test': true }); + assert(result.test); + }); + + it('Should execute command', async () => { + const command = new Command({}); + await command.execute(); + }); + }); + + describe('Group Command', function () { + it('Should execute provided option', async () => { + const commandOptions = [{ name: 'test', process: (args) => { return args; } }]; + const groupCommand = new GroupCommand({ options: commandOptions }); + await groupCommand.execute({ 'test': true }); + + assert(groupCommand.hasBeenExecuted); + }); + + it('Should set hasBeenExecuted to false if no option has been provided', async () => { + const groupCommand = new GroupCommand({}); + await groupCommand.execute(); + + assert(!groupCommand.hasBeenExecuted); + }); + }); + + describe('Common', function () { + describe('Logger', function () { + it('Should print all type of logs', async () => { + const originalConsoleLog = console.log; + console.log = () => { assert(true); } + + logger.log('message'); + logger.success('message'); + logger.info('message'); + logger.error('message', 'error'); + + console.log = originalConsoleLog; + }); + }); + + describe('Table', function () { + + it('Should add a row', async () => { + const table = new Table(['head']); + table.addRow(['row']); + + assert(table.table[0][0] == 'row'); + }); + + it('Should add a section', async () => { + const table = new Table(['head']); + table.addSection('sectionName', [['row']]); + + assert(Array.isArray(table.table[0]['\u001b[96msectionName\u001b[39m'])); + assert(table.table[1][0] == ''); + assert(table.table[1][1] == 'row'); + }); + + it('Should visualize the table', async () => { + testLogger.hide(sinon); + testLogger.on('log', (table) => { + assert(table.includes('sectionName')); + assert(table.includes('row')); + }); + + const table = new Table(['head']); + table.addSection('sectionName', [['row']]); + table.draw(); + + sinon.restore(); + }); + }); + }); + + describe('Helpers', function () { + describe('AsyncSoftExec', function () { + function stubChildProcess (error, stdout) { + const AsyncSoftExec = proxyquire('../../cli-commands/helpers/async-soft-exec', { + 'child_process': { + exec: (command, cb) => { cb(error, stdout); } + } + }); + + return AsyncSoftExec; + } + + it('Should execute command', async () => { + const AsyncSoftExec = stubChildProcess('', 'executed'); + const asyncSoftExec = new AsyncSoftExec('Test'); + + const result = await asyncSoftExec.exec(); + assert(result == 'executed'); + }); + + it('Should throw error when executing', async () => { + const AsyncSoftExec = stubChildProcess('Fake error', ''); + const asyncSoftExec = new AsyncSoftExec('Test'); + + try { + await asyncSoftExec.exec(); + assert(false); + } catch (error) { + assert(error == 'Fake error'); + } + }); + }); + + describe('File system utils', function () { + + function stubExistsSync (output) { + return { existsSync: () => { return output; } } + } + + function stubMkdirSync (cb) { + return { mkdirSync: (dirName) => { cb(dirName); } } + } + + function stubCopyFileSync (cb) { + return { copyFileSync: (source, destination) => { cb(source, destination); } } + } + + function stubReaddir (...output) { + return { readdir: (dir, cb) => { cb(...output); } } + } + + function stubLstatSyncFile (output) { + return { lstatSync: () => { return { isFile: () => { return output; } } } } + } + + function stubLstatSyncDir (output) { + return { lstatSync: () => { return { isDirectory: () => { return output; } } } } + } + + function stubReadFileSync () { + return { readFileSync: (filePath) => { return filePath; } } + } + + function stubWriteFileSync (cb) { + return { writeFileSync: (filePath, fileContent) => { cb(filePath, fileContent); } } + } + + function stubUnlinkSync () { + return { unlinkSync: () => { } } + } + + function stubRmdirSync () { + return { rmdirSync: () => { } } + } + + it('Should create dir', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubExistsSync(false), + ...stubMkdirSync((dir) => { + assert(dir == 'dir'); + }) + } + }); + + utils.createDir('dir'); + }); + + it('Should copy file', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubExistsSync(false), + ...stubCopyFileSync((src, dst) => { + assert(src == 'here'); + assert(dst == 'there'); + }) + } + }); + + utils.copyFileFromTo('here', 'there'); + }); + + it('Should copy all files in dir', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubExistsSync(true), + ...stubCopyFileSync((src, dst) => { + assert(src == 'src/file'); + assert(dst == 'dst/file'); + }), + ...stubReaddir(undefined, ['file']) + } + }); + + utils.copyAllFilesFromDirTo('src/', 'dst/'); + }); + + it('Should throw if coping of all files in dir fails', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubExistsSync(true), + ...stubCopyFileSync((src, dst) => { + assert(src == 'src/file'); + assert(dst == 'dst/file'); + }), + ...stubReaddir('error', []) + } + }); + + try { + utils.copyAllFilesFromDirTo('src/', 'dst/'); + assert(false); + } catch (error) { + assert(error.message == 'Example files can not be copied'); + } + + }); + + it('Should return if path is dir', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubLstatSyncDir(true), + } + }); + + assert(utils.isDir('dir')); + }); + + it('Should return if path is a file', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubLstatSyncFile(true) + } + }); + + assert(utils.isFile('path')); + }); + + it('Should read file', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubReadFileSync(true), + } + }); + + assert(utils.readFile('file') == 'file'); + }); + + it('Should read dir with files only', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubLstatSyncDir(false), + ...stubReaddir(undefined, ['file']) + } + }); + + const result = await utils.recursivelyReadDir('dir'); + assert(result[0].fullPath == 'dir/file'); + }); + + it('Should read dir with sub dirs', async () => { + let dirLevel = 2; + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + lstatSync: () => { return { isDirectory: () => { dirLevel--; return dirLevel; } } }, + ...stubReaddir(undefined, ['file']) + } + }); + + const result = await utils.recursivelyReadDir('dir') + assert(result[0].fullPath == 'dir/file/file'); + }); + + it('Should throw if reading of a dir fails', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubReaddir({ message: 'Error' }, []) + } + }); + + try { + await utils.recursivelyReadDir('dir'); + assert(false); + } catch (error) { + assert(error == 'Error'); + } + }); + + it('Should clean up dir containing only files', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubLstatSyncDir(false), + ...stubReaddir(undefined, ['file']), + ...stubUnlinkSync(), + ...stubRmdirSync() + } + }); + + await utils.recursivelyDeleteDir('dir'); + }); + + it('Should clean up dir containing sub dirs', async () => { + let dirLevel = 2; + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + lstatSync: () => { return { isDirectory: () => { dirLevel--; return dirLevel; } } }, + ...stubReaddir(undefined, ['file']), + ...stubRmdirSync(), + ...stubUnlinkSync() + } + }); + + await utils.recursivelyDeleteDir('dir'); + }); + + it('Should throw if deletion of a dir fails', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubReaddir({ message: 'Error' }, []) + } + }); + + try { + await utils.recursivelyDeleteDir('dir'); + assert(false); + } catch (error) { + assert(error == 'Error'); + } + }); + + it('Should write to a file', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubWriteFileSync((path, content) => { + assert(path == 'path'); + assert(content == 'content'); + }), + } + }); + + utils.writeFile('path', 'content'); + }); + + it('Should throw if writing to a file fails', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubWriteFileSync(() => { throw new Error('Fake error'); }) + } + }); + + try { + utils.writeFile('path', 'content'); + assert(false); + } catch (error) { + assert(error.message.includes('Fake error')); + } + }); + + it('Should remove a file', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubUnlinkSync(true), + } + }); + + utils.rmFile('file'); + }); + + it('Should remove dir', async () => { + const utils = proxyquire('../../cli-commands/helpers/file-system-util', { + 'fs': { + ...stubRmdirSync(true), + } + }); + + utils.rmDir('dir'); + }); + }); + }); +}); diff --git a/tests/cli-commands/nodeos-tests.js b/tests/cli-commands/nodeos-tests.js new file mode 100644 index 0000000..b3692dd --- /dev/null +++ b/tests/cli-commands/nodeos-tests.js @@ -0,0 +1,524 @@ +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const Command = require('../../cli-commands/commands/command'); +const GroupCommand = require('../../cli-commands/commands/group-command'); +const NodeosCommand = require('../../cli-commands/commands/nodeos/index'); + +// Sub-commands +const LogsCommand = require('../../cli-commands/commands/nodeos/subcommands/logs/index'); +const StopCommand = require('../../cli-commands/commands/nodeos/subcommands/stop/index'); +const StartCommand = require('../../cli-commands/commands/nodeos/subcommands/start/index'); +const AccountsCommand = require('../../cli-commands/commands/nodeos/subcommands/accounts/index'); + +// Definitions +const logsDefinition = require('../../cli-commands/commands/nodeos/subcommands/logs/definition'); +const stopDefinition = require('../../cli-commands/commands/nodeos/subcommands/stop/definition'); +const startDefinition = require('../../cli-commands/commands/nodeos/subcommands/start/definition'); +const nodeosDefinition = require('../../cli-commands/commands/nodeos/definition'); +const accountsDefinition = require('../../cli-commands/commands/nodeos/subcommands/accounts/definition'); + +// Options +const PathOption = require('../../cli-commands/commands/nodeos/subcommands/start/options/path-option'); +const LinesOption = require('../../cli-commands/commands/nodeos/subcommands/logs/options/lines-option'); + +// Common & Specifics +const dataManager = require('../../cli-commands/commands/nodeos/specific/nodeos-data/data-manager'); +const predefinedAccounts = require('../../cli-commands/commands/nodeos/subcommands/common/accounts'); + +const Logger = require('./utils/logger'); +const logger = new Logger(); + +describe('Nodeos Command', function () { + + beforeEach(async () => { + logger.hide(sinon); + }); + + afterEach(async () => { + sinon.restore(); + }); + + describe('Command', function () { + + it('Should initialize command properly', async () => { + let nodeosCommand = new NodeosCommand(); + + assert(nodeosCommand instanceof GroupCommand); + assert(nodeosCommand.template == nodeosDefinition.template); + assert(nodeosCommand.description = nodeosDefinition.description); + assert(nodeosCommand.options == nodeosDefinition.options); + assert(nodeosCommand.subcommands.length > 0); + }); + }); + + describe('Sub-commands', function () { + + function stubNodeosDataManager (nodeosRunning, checkPoints) { + return { + '../../specific/nodeos-data/data-manager': { + nodeosIsRunning: () => { return nodeosRunning; }, + nodeosPath: () => { return ''; }, + setNodeosPath: (path) => { checkPoints.setNodeosPath--; }, + requireRunningNodeos: () => { checkPoints.requireRunningNodeos--; } + } + } + } + + function stubAsyncSoftExec (checkPoints) { + return { + '../../../../helpers/async-soft-exec': class FakeAsyncSoftExec { + constructor () { } + exec () { checkPoints.exec--; } + } + } + } + + describe('Start Command', function () { + let startCommand; + + beforeEach(async () => { + startCommand = new StartCommand(); + }); + + it('Should initialize command properly', async () => { + assert(startCommand instanceof Command); + assert(startCommand.template == startDefinition.template); + assert(startCommand.description = startDefinition.description); + assert(startCommand.options == startDefinition.options); + assert(startCommand.subcommands.length == 0); + }); + + function stubPredefinedAccounts (checkPoints) { + return { + '../common/accounts': { + load: () => { checkPoints.load--; } + } + } + } + + it('Should log in case another nodeos instance has been run already', async () => { + const startCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/start/index', { + ...stubNodeosDataManager(true) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('info', (message) => { + if (message.includes('Nodeos is already running')) { + return resolve(true); + } + }); + + await startCommand.execute(); + }); + + await waitToPass; + }); + + it('Should start nodeos', async () => { + const checkPoints = { + setNodeosPath: 1, + requireRunningNodeos: 1, + load: 1, + exec: 1 + } + + const startCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/start/index', { + ...stubNodeosDataManager(false, checkPoints), + ...stubAsyncSoftExec(checkPoints), + ...stubPredefinedAccounts(checkPoints) + })); + + await startCommand.execute({ path: './' }); + + assert(checkPoints.setNodeosPath == 0); + assert(checkPoints.requireRunningNodeos == 0); + assert(checkPoints.load == 0); + assert(checkPoints.exec == 0); + }); + + it('Should log in case of an error', async () => { + const startCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/start/index', { + '../../command': class FakeCommand { + processOptions () { throw new Error('Fake error') } + }, + ...stubNodeosDataManager(false) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Nodeos has not been started')) { + return resolve(true); + } + }); + + await startCommand.execute(); + }); + + await waitToPass; + }); + + describe('Options', function () { + describe('Path', function () { + it('Should return provided path', async () => { + const path = await PathOption.process('./'); + assert(path == './'); + }); + + it('Should throw in case the provided path is an incorrect one', async () => { + try { + await PathOption.process('./custom'); + assert(false); + } catch (error) { + assert(error.message.includes('no such file or directory, lstat \'./custom\'')); + } + }); + }); + }); + }); + + describe('Stop Command', function () { + let stopCommand; + + beforeEach(async () => { + stopCommand = new StopCommand(); + }); + + function stubFileSystemUtils (checkPoints) { + return { + '../../../../helpers/file-system-util': { + rmFile: (dirPath) => { checkPoints.rmFile--; }, + recursivelyDeleteDir: () => { checkPoints.recursivelyDeleteDir--; }, + readFile: () => { checkPoints.readFile--; } + } + } + } + + it('Should initialize command properly', async () => { + assert(stopCommand instanceof Command); + assert(stopCommand.template == stopDefinition.template); + assert(stopCommand.description = stopDefinition.description); + assert(stopCommand.options == stopDefinition.options); + assert(stopCommand.subcommands.length == 0); + }); + + it('Should stop nodeos instance', async () => { + const checkPoints = { + exec: 1, + rmFile: 2, + readFile: 1, + recursivelyDeleteDir: 2 + } + + const dataManager = stubNodeosDataManager(true); + const asyncSoftExec = stubAsyncSoftExec(checkPoints); + const fileSystemUtils = stubFileSystemUtils(checkPoints); + + const stopCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/stop/index', { + ...dataManager, + ...fileSystemUtils, + ...asyncSoftExec + })); + + await stopCommand.execute(); + assert(checkPoints.exec == 0); + assert(checkPoints.rmFile == 0); + assert(checkPoints.readFile == 0); + assert(checkPoints.recursivelyDeleteDir == 0); + }); + + it('Should clear only nodeos data in case of no running nodeos instance', async () => { + const checkPoints = { + exec: 1, + rmFile: 2, + readFile: 1, + recursivelyDeleteDir: 2 + } + + const dataManager = stubNodeosDataManager(false); + const asyncSoftExec = stubAsyncSoftExec(checkPoints); + const fileSystemUtils = stubFileSystemUtils(checkPoints); + + const stopCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/stop/index', { + ...dataManager, + ...fileSystemUtils, + ...asyncSoftExec + })); + + await stopCommand.execute(); + assert(checkPoints.exec == 1); + assert(checkPoints.rmFile == 0); + assert(checkPoints.readFile == 1); + assert(checkPoints.recursivelyDeleteDir == 0); + }); + + it('Should log in case of an error', async () => { + const stopCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/stop/index', { + '../../specific/nodeos-data/data-manager': { + nodeosIsRunning: () => { throw new Error(); }, + nodeosPath: () => { return ''; } + } + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Nodeos has not been stopped')) { + return resolve(true); + } + }); + + + await stopCommand.execute(); + }); + + await waitToPass; + }); + }); + + describe('Logs Command', function () { + let logsCommand; + + beforeEach(async () => { + logsCommand = new LogsCommand(); + }); + + it('Should initialize command properly', async () => { + assert(logsCommand instanceof Command); + assert(logsCommand.template == logsDefinition.template); + assert(logsCommand.description = logsDefinition.description); + assert(logsCommand.options == logsDefinition.options); + assert(logsCommand.subcommands.length == 0); + }); + + it('Should log in case of no running nodeos instance', async () => { + const dataManager = stubNodeosDataManager(false); + + const logsCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/logs/index', { + ...dataManager + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('info', (message) => { + if (message.includes('Empty logs')) { + return resolve(true); + } + }); + + await logsCommand.execute(); + }); + + await waitToPass; + }); + + it('Should display nodeos logs', async () => { + const checkPoints = { + exec: 1 + } + + const dataManager = stubNodeosDataManager(true); + const asyncSoftExec = stubAsyncSoftExec(checkPoints); + + const logsCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/logs/index', { + ...dataManager, + ...asyncSoftExec + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('success', (message) => { + if (message.includes('Nodeos logs')) { + return resolve(true); + } + }); + + await logsCommand.execute({ lines: 1 }); + }); + + await waitToPass; + assert(checkPoints.exec == 0); + }); + + it('Should log in case of an error', async () => { + const logsCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/logs/index', { + '../../../command': class FakeCommand { + processOptions () { + throw new Error(); + } + }, + ...stubNodeosDataManager(true) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Logs has not been shown')) { + return resolve(true); + } + }); + + await logsCommand.execute({ lines: 1 }); + }); + + await waitToPass; + }); + + describe('Options', function () { + describe('Number of lines', function () { + it('Should return provided number of lines', async () => { + assert(LinesOption.process(1) == 1); + assert(LinesOption.definition.default == 10); + }); + }); + }); + }); + + describe('Accounts Command', function () { + let accountsCommand; + + beforeEach(async () => { + accountsCommand = new AccountsCommand(); + }); + + it('Should initialize command properly', async () => { + assert(accountsCommand instanceof Command); + assert(accountsCommand.template == accountsDefinition.template); + assert(accountsCommand.description = accountsDefinition.description); + assert(accountsCommand.options == accountsDefinition.options); + assert(accountsCommand.subcommands.length == 0); + }); + + it('Should display a table with preloaded accounts', async () => { + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('log', (message) => { + for (let i = 0; i < predefinedAccounts.length; i++) { + assert(message.includes(predefinedAccounts[i].name)); + assert(message.includes(predefinedAccounts[i].publicKey)); + assert(message.includes(predefinedAccounts[i].privateKey)); + } + + return resolve(true); + }); + + await accountsCommand.execute(); + }); + + await waitToPass; + }); + + it('Should log in case of an error', async () => { + const accountsCommand = new (proxyquire('../../cli-commands/commands/nodeos/subcommands/accounts/index', { + '../common/accounts': { + accounts () { + throw new Error(); + } + } + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Accounts has not been shown')) { + return resolve(true); + } + }); + + await accountsCommand.execute(); + }); + + await waitToPass; + }); + + }); + }); + + describe('Command specifics', function () { + + describe('Data Manager', function () { + + function stubFileSystemUtilsCallback (cb) { + return proxyquire('../../cli-commands/commands/nodeos/specific/nodeos-data/data-manager', { + '../../../../helpers/file-system-util': { + writeFile: (path, content) => { return cb(path, content) }, + readFile: (filePath) => { return cb(filePath) } + } + }); + } + + it('Should return dirname as default path', async () => { + assert(dataManager.defaultPath().includes('eoslime/cli-commands/commands/nodeos/specific/nodeos-data')); + }); + + it('Should return the last set nodeosPath', async () => { + assert(dataManager.nodeosPath()); + }); + + it('Should set new nodeos path', async () => { + const dataManagerStub = stubFileSystemUtilsCallback((path, content) => { + assert(path.includes('eoslime/cli-commands/commands/nodeos/specific/nodeos-data/nodeos.json')); + assert(content == '{"nodeosPath":"./custom"}'); + }); + + dataManagerStub.setNodeosPath('./custom'); + }); + + it('Should return if nodeos is running', async () => { + const dataManagerStub = stubFileSystemUtilsCallback((path) => { + assert('./custom/eosd.pid' == path); + return { toString: () => { return 'content'; } } + }); + + dataManagerStub.nodeosIsRunning('./custom'); + }); + + it('Should throw in case nodeos is not running', async () => { + try { + dataManager.nodeosIsRunning = () => { return false; } + dataManager.requireRunningNodeos('./custom'); + + assert(false); + } catch (error) { + assert(error.message === 'Check if another nodeos has been started already'); + } + }); + }); + + describe('Pre-defined accounts', function () { + + it('Should return pre-defined accounts', async () => { + const accounts = predefinedAccounts.accounts(); + + assert(accounts.length == 3); + + assert(accounts[0].name == 'eoslimedavid'); + assert(accounts[0].publicKey == 'EOS7UyV15G2t47MqRm4WpUP6KTfy9sNU3HHGu9aAgR2A3ktxoBTLv'); + assert(accounts[0].privateKey == '5KS9t8LGsaQZxLP6Ln5WK6XwYU8M3AYHcfx1x6zoGmbs34vQsPT'); + + assert(accounts[1].name == 'eoslimekevin'); + assert(accounts[1].publicKey == 'EOS6Zz4iPbjm6FNys1zUMaRE4zPXrHcX3SRG65YWneVbdXQTSiqDp'); + assert(accounts[1].privateKey == '5KieRy975NgHk5XQfn8r6o3pcqJDF2vpeV9bDiuB5uF4xKCTwRF'); + + assert(accounts[2].name == 'eoslimemarty'); + assert(accounts[2].publicKey == 'EOS7FDeYdY3G8yMNxtrU8MSYnAJc3ZogYHgL7RG3rBf8ZDYA3xthi'); + assert(accounts[2].privateKey == '5JtbCXgK5NERDdFdrmxb8rpYMkoxVfSyH1sR6TYxHBG5zNLHfj5'); + }); + + it('Should create pre-defined accounts on the chain', async () => { + let numberOfCreations = 3; + + // Stub eoslime + const accounts = proxyquire('../../cli-commands/commands/nodeos/subcommands/common/accounts', { + '../../../../../': { + init: () => { + return { + Account: { + create: () => { numberOfCreations--; } + } + } + } + } + }); + + await accounts.load(); + assert(numberOfCreations == 0); + }); + }); + }); +}); diff --git a/tests/cli-commands/shape-tests.js b/tests/cli-commands/shape-tests.js new file mode 100644 index 0000000..6743980 --- /dev/null +++ b/tests/cli-commands/shape-tests.js @@ -0,0 +1,104 @@ +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const definition = require('../../cli-commands/commands/shape/definition'); + +const Command = require('../../cli-commands/commands/command'); +const ShapeCommand = require('../../cli-commands/commands/shape/index'); + +const FrameworkOption = require('../../cli-commands/commands/shape/options/framework-option'); + +const Logger = require('./utils/logger'); +const logger = new Logger(); + +describe('Shape Command', function () { + + beforeEach(async () => { + logger.hide(sinon); + }); + + afterEach(async () => { + sinon.restore(); + }); + + describe('Command', function () { + + it('Should initialize command properly', async () => { + const shapeCommand = new ShapeCommand(); + + assert(shapeCommand instanceof Command); + assert(shapeCommand.template == definition.template); + assert(shapeCommand.description = definition.description); + assert(shapeCommand.options == definition.options); + assert(shapeCommand.subcommands.length == 0); + }); + + it('Should prepare shape ', async () => { + const shapeCommand = new (proxyquire('../../cli-commands/commands/shape/index', + { + 'simple-git/promise': () => { + return { clone: () => { assert(true); } } + } + } + )); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('success', (message) => { + if (message.includes('Successful shaping')) { + return resolve(true); + } + }); + + await shapeCommand.execute({ framework: 'react' }); + }); + + await waitToPass; + }); + + function stubBaseCommand (cb) { + return { + '../command': class FakeBaseCommand { + processOptions () { + return cb(); + } + } + } + } + + it('Should log in case of an error', async () => { + const shapeCommand = new (proxyquire('../../cli-commands/commands/shape/index', { + ...stubBaseCommand(() => { throw new Error('Fake error'); }) + })); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message) => { + if (message.includes('Unsuccessful shaping')) { + return resolve(true); + } + }); + + await shapeCommand.execute(); + }); + + await waitToPass; + }); + }); + + describe('Options', function () { + describe('Framework', function () { + + it('Should return the repository of the shape framework', async () => { + assert(FrameworkOption.process('react') == 'https://github.com/LimeChain/eoslime-shape-react.git'); + }); + + it('Should throw in case the provided framework is not supported', async () => { + try { + await FrameworkOption.process('Not supported'); + } catch (error) { + assert(error.message == 'Invalid Shape framework'); + } + }); + }); + }); +}); diff --git a/tests/cli-commands/test-tests.js b/tests/cli-commands/test-tests.js new file mode 100644 index 0000000..0c82748 --- /dev/null +++ b/tests/cli-commands/test-tests.js @@ -0,0 +1,303 @@ +const path = require('path'); +const sinon = require('sinon'); +const assert = require('assert'); +const proxyquire = require('proxyquire').noCallThru(); + +const Command = require('../../cli-commands/commands/command'); +const TestCommand = require('../../cli-commands/commands/test/index'); + +const NetworkOption = require('../../cli-commands/commands/test/options/network-option'); +const ResourceReportOption = require('../../cli-commands/commands/test/options/resource-usage-option'); + +const definition = require('../../cli-commands/commands/test/definition'); + +const Logger = require('./utils/logger'); +const logger = new Logger(); + +const eoslime = require('../../').init(); +const MochaFramework = require('../../cli-commands/commands/test/specific/test-frameworks/mocha'); + +describe('Test Command', function () { + + this.timeout(10000); + + beforeEach(async () => { + logger.hide(sinon); + }); + + afterEach(async () => { + sinon.restore(); + }); + + describe('Command', function () { + + it('Should initialize command properly', async () => { + const testCommand = new TestCommand(); + assert(testCommand instanceof Command); + assert(testCommand.template == definition.template); + assert(testCommand.description = definition.description); + assert(testCommand.options == definition.options); + assert(testCommand.subcommands.length == 0); + }); + + it('Should run test scripts', async () => { + const checkPoint = { + runTests: 1, + setDescribeArgs: 1 + } + + const testCommand = new TestCommand(class TestFramework { + setDescribeArgs () { checkPoint.setDescribeArgs--; } + runTests () { checkPoint.runTests--; } + }); + + const args = {}; + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('success', (message) => { + if (message.includes('Testing completed successfully')) { + return resolve(true); + } + }); + + await testCommand.execute(args); + }); + + await waitToPass; + + assert(args.eoslime.tests); + assert(checkPoint.runTests == 0); + assert(checkPoint.setDescribeArgs == 0); + }); + + it('Should log error message in case of error', async () => { + // Because no test framework has been provided, it will throw + const testCommand = new TestCommand(); + + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('error', (message, error) => { + if (message.includes('Testing failed')) { + return resolve(true); + } + }); + + await testCommand.execute(); + }); + + await waitToPass; + }); + }); + + describe('Options', function () { + + describe('Path', function () { + + function stubFileSystemUtils (isFolder) { + return { + '../../../helpers/file-system-util': { + isDir: () => { return isFolder; }, + isFile: () => { return !isFolder; }, + recursivelyReadDir: (dir) => { + return [{ fullPath: `${dir}/fullPath` }]; + } + } + } + } + + it('Should load a test script into the test framework', async () => { + const pathOption = proxyquire('../../cli-commands/commands/test/options/path-option', { + ...stubFileSystemUtils(false) + }); + + await pathOption.process('./custom.js', + { + testFramework: { + addTestFiles: (files) => { assert(files[0] == path.resolve('./', './custom.js')); } + } + } + ); + }); + + it('Should load all test scripts from a folder into the test framework', async () => { + const pathOption = proxyquire('../../cli-commands/commands/test/options/path-option', { + ...stubFileSystemUtils(true) + }); + + await pathOption.process('./custom', + { + testFramework: { + addTestFiles: (files) => { assert(files[0] == './custom/fullPath'); } + } + } + ); + }); + }); + + describe('Network', function () { + const eoslime = require('../../').init(); + + it('Should set eoslime on provided network', async () => { + NetworkOption.process('jungle', { eoslime }); + assert(eoslime.Provider.network.name == 'jungle'); + }); + + it('Should set eoslime on local network none has been provided', async () => { + NetworkOption.process(undefined, { eoslime }); + assert(eoslime.Provider.network.name == 'local'); + }); + }); + + describe('Resources usage report', function () { + + const FAUCET_ABI_PATH = "./tests/testing-contracts/compiled/faucet.abi"; + const FAUCET_WASM_PATH = "./tests/testing-contracts/compiled/faucet.wasm"; + + it('Should show correct data in report', async () => { + const testFramework = new MochaFramework(); + + const waitToPass = new Promise(async (resolve, reject) => { + const contractAccount = await eoslime.Account.createRandom(); + + // The result is a table visualization + // Because of that we are waiting for the table output to check if all good + logger.on('log', () => { + const table = ResourceReportOption.reportTable.table; + assert(JSON.stringify(table.options.head) == JSON.stringify([ + '', + 'Contract', + 'Action', + 'CPU ( MIN | MAX )', + 'NET ( MIN | MAX )', + 'RAM ( MIN | MAX )', + 'Calls' + ])); + + assert(table[1][1] == contractAccount.name); + assert(table[3][1] == contractAccount.name); + assert(table[4][2] == 'test'); + + return resolve(true); + }); + + ResourceReportOption.process(true, { eoslime, testFramework }); + + const contract = await eoslime.Contract.deployOnAccount(FAUCET_WASM_PATH, FAUCET_ABI_PATH, contractAccount); + await contract.actions.test(); + + testFramework.eventsHooks[0].callback(); + }); + + await waitToPass; + }); + }); + }); + + describe('Command specifics', function () { + + describe('Test utils', function () { + + async function expect (promise, expectCb) { + const testCommand = new TestCommand(class TestFramework { + setDescribeArgs () { } + runTests () { } + }); + + const args = {}; + const waitToPass = new Promise(async (resolve, reject) => { + logger.on('success', async () => { + try { + await args.eoslime.tests[expectCb]( + promise + ) + resolve(true); + } catch (error) { + reject(error); + } + }); + + await testCommand.execute(args); + }); + + await waitToPass; + } + + describe('expectAssert', function () { + + it('Should expectAssert', async () => { + await expect( + new Promise((reject, resolve) => { + throw new Error('eosio_assert_message_exception'); + }), + 'expectAssert' + ); + }); + + it('Should throw in case unexpected error happens', async () => { + try { + await expect( + new Promise((resolve, reject) => { + throw new Error('Another error'); + }), + 'expectAssert' + ); + assert(false); + } catch (error) { + assert(error.message.includes('Expected assert, got \'Another error\' instead')); + } + }); + + it('Should throw in case assert error has not been received', async () => { + try { + await expect( + new Promise((resolve, reject) => { + resolve(true); + }), + 'expectAssert' + ); + assert(false); + } catch (error) { + assert(error.message.includes('Expected assert not received')); + } + }); + }); + + describe('expectMissingAuthority', function () { + it('Should expectMissingAuthority', async () => { + await expect( + new Promise((reject, resolve) => { + throw new Error('missing_auth_exception'); + }), + 'expectMissingAuthority' + ); + }); + + it('Should throw in case unexpected error happens', async () => { + try { + await expect( + new Promise((resolve, reject) => { + throw new Error('Another error'); + }), + 'expectMissingAuthority' + ); + assert(false); + } catch (error) { + assert(error.message.includes('Expected missing authority, got \'Another error\' instead')); + } + }); + + it('Should throw in case missing authority error has not been received', async () => { + try { + await expect( + new Promise((resolve, reject) => { + resolve(true); + }), + 'expectMissingAuthority' + ); + assert(false); + } catch (error) { + assert(error.message.includes('Expected missing authority not received')); + } + }); + }); + }); + }); +}); diff --git a/tests/cli-commands/utils/logger.js b/tests/cli-commands/utils/logger.js new file mode 100644 index 0000000..38ad196 --- /dev/null +++ b/tests/cli-commands/utils/logger.js @@ -0,0 +1,55 @@ +const logger = require('../../../cli-commands/common/logger'); + +class Logger { + + constructor () { + this.events = {} + } + + hide (sinon) { + const self = this + sinon.stub(logger, 'log').callsFake((message) => { + if (self.events['log']) { + for (let i = 0; i < self.events['log'].length; i++) { + self.events['log'][i](message) + } + } + }); + + sinon.stub(logger, 'info').callsFake((message) => { + if (self.events['info']) { + for (let i = 0; i < self.events['info'].length; i++) { + self.events['info'][i](message) + } + } + }); + + sinon.stub(logger, 'success').callsFake((message) => { + if (self.events['success']) { + for (let i = 0; i < self.events['success'].length; i++) { + self.events['success'][i](message) + } + } + }); + + sinon.stub(logger, 'error').callsFake((message, error) => { + if (self.events['error']) { + for (let i = 0; i < self.events['error'].length; i++) { + self.events['error'][i](message) + } + } + + // throw new Error(error.message); + }); + } + + on (event, cb) { + if (!this.events[event]) { + this.events[event] = []; + } + + this.events[event].push(cb) + } +} + +module.exports = Logger; diff --git a/tests/contract-tests.js b/tests/contract-tests.js index 15d9c37..5f827a0 100644 --- a/tests/contract-tests.js +++ b/tests/contract-tests.js @@ -29,7 +29,7 @@ describe("Contract", function () { try { const tokenAccount = await eoslime.Account.createRandom(); tokenContract = await eoslime.Contract.deployOnAccount(TOKEN_WASM_PATH, TOKEN_ABI_PATH, tokenAccount); - await tokenContract.create(faucetAccount.name, TOTAL_SUPPLY); + await tokenContract.actions.create([faucetAccount.name, TOTAL_SUPPLY]); } catch (error) { console.log(error); } @@ -60,8 +60,8 @@ describe("Contract", function () { it("Should instantiate correct instance of Contract from ABI file", async () => { const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); - assert(typeof faucetContract.produce == "function"); - assert(typeof faucetContract.withdraw == "function"); + assert(typeof faucetContract.actions.produce == "function"); + assert(typeof faucetContract.actions.withdraw == "function"); assert(faucetContract.name == faucetAccount.name); assert(JSON.stringify(faucetContract.executor) == JSON.stringify(faucetAccount)); @@ -71,8 +71,8 @@ describe("Contract", function () { it("Should instantiate correct instance of Contract from blockchain account name", async () => { const faucetContract = await eoslime.Contract.at(faucetAccount.name, faucetAccount); - assert(typeof faucetContract.produce == "function"); - assert(typeof faucetContract.withdraw == "function"); + assert(typeof faucetContract.actions.produce == "function"); + assert(typeof faucetContract.actions.withdraw == "function"); assert(faucetContract.name == faucetAccount.name); assert(JSON.stringify(faucetContract.executor) == JSON.stringify(faucetAccount)); @@ -92,7 +92,7 @@ describe("Contract", function () { const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, "INVALID"); eoslime.Provider.defaultAccount = ''; - await faucetContract.produce(tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"]); assert(false, "Should throw"); } catch (error) { @@ -157,9 +157,9 @@ describe("Contract", function () { const tokensHolder = await eoslime.Account.createRandom(); // faucetAccount is the executor - await faucetContract.produce(tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"]); - const result = await faucetContract.withdrawers.limit(1).equal(tokensHolder.name).find(); + const result = await faucetContract.tables.withdrawers.limit(1).equal(tokensHolder.name).find(); assert(result[0].quantity == PRODUCED_TOKENS_AMOUNT); assert(result[0].token_name == tokenContract.name); @@ -170,7 +170,7 @@ describe("Contract", function () { const tokensHolder = await eoslime.Account.createRandom(); const executor = await eoslime.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo", { from: executor }); + await faucetContract.actions.produce([tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"], { from: executor }); // After the execution, the contract executor should be the same as the initially provided one assert(faucetContract.executor.name == faucetAccount.name); @@ -181,8 +181,8 @@ describe("Contract", function () { const tokensHolder = await eoslime.Account.createRandom(); const executor = await eoslime.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo', { from: executor, unique: true }); - await faucetContract.produce(tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo', { from: executor, unique: true }); + await faucetContract.actions.produce([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { from: executor, unique: true }); + await faucetContract.actions.produce([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { from: executor, unique: true }); assert(true); }); @@ -192,52 +192,102 @@ describe("Contract", function () { const executor = await eoslime.Account.createRandom(); try { - await faucetContract.produce(tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo', { from: executor }); - await faucetContract.produce(tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo', { from: executor }); + await faucetContract.actions.produce([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { from: executor }); + await faucetContract.actions.produce([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { from: executor }); } catch (error) { assert(error.includes('duplicate transaction')); } }); - it('Should get the raw transaction from the action', async () => { + function assertRawTransaction (tx, contractName) { + assert(tx.expiration != undefined); + assert(tx.ref_block_num != undefined); + assert(tx.ref_block_prefix != undefined); + assert(tx.max_net_usage_words != undefined); + assert(tx.max_cpu_usage_ms != undefined); + assert(tx.delay_sec != undefined); + assert(tx.context_free_actions != undefined); + assert(tx.actions != undefined); + assert(tx.actions[0].name == 'produce'); + assert(tx.actions[0].account == contractName); + assert(tx.actions[0].data != undefined); + assert(tx.actions[0].authorization != undefined); + } + + it('Should get a raw transaction from action', async () => { + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const tokensHolder = await eoslime.Account.createRandom(); + + const rawActionTx = await faucetContract.actions.produce.getRawTransaction([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo']); + assertRawTransaction(rawActionTx, faucetContract.name); + }); + + it('Should get a raw transaction from payable action', async () => { + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const tokensHolder = await eoslime.Account.createRandom(); + + const rawActionTx = await faucetContract.actions.produce.getRawTransaction([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { tokens: '5.0000 SYS' }); + + assertRawTransaction(rawActionTx, faucetContract.name); + assert(rawActionTx.actions[1].name == 'transfer'); + assert(rawActionTx.actions[1].account == 'eosio.token'); + assert(rawActionTx.actions[1].data != undefined); + assert(rawActionTx.actions[1].authorization != undefined); + }); + + it('Should get a raw transaction from unique action', async () => { + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const tokensHolder = await eoslime.Account.createRandom(); + + const rawActionTx = await faucetContract.actions.produce.getRawTransaction([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { unique: true }); + + assertRawTransaction(rawActionTx, faucetContract.name); + assert(rawActionTx.actions[1].name == 'nonce'); + assert(rawActionTx.actions[1].account == 'eosio.null'); + assert(rawActionTx.actions[1].data != undefined); + assert(rawActionTx.actions[1].authorization != undefined); + }); + + function assertSignedTransaction (tx, contractName) { + assert(tx.signatures.length == 1); + assertRawTransaction(tx.transaction, contractName); + } + + it('Should sign an action without broadcasting it', async () => { + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const tokensHolder = await eoslime.Account.createRandom(); + const signer = await eoslime.Account.createRandom(); + + const signedActionTx = await faucetContract.actions.produce.sign([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { from: signer }); + assertSignedTransaction(signedActionTx, faucetContract.name); + }); + + it('Should sign a payable action ', async () => { const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); const tokensHolder = await eoslime.Account.createRandom(); + const signer = await eoslime.Account.createRandom(); + + const signedActionTx = await faucetContract.actions.produce.sign([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { tokens: '5.0000 SYS', from: signer }); - const rawActionTx = await faucetContract.produce.getRawTransaction(tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'); - assert(rawActionTx.expiration != undefined); - assert(rawActionTx.ref_block_num != undefined); - assert(rawActionTx.ref_block_prefix != undefined); - assert(rawActionTx.max_net_usage_words != undefined); - assert(rawActionTx.max_cpu_usage_ms != undefined); - assert(rawActionTx.delay_sec != undefined); - assert(rawActionTx.context_free_actions != undefined); - assert(rawActionTx.actions != undefined); - assert(rawActionTx.actions[0].name == 'produce'); - assert(rawActionTx.actions[0].account == faucetContract.name); - assert(rawActionTx.actions[0].data != undefined); - assert(rawActionTx.actions[0].authorization != undefined); + assertSignedTransaction(signedActionTx, faucetContract.name); + assert(signedActionTx.transaction.actions[1].name == 'transfer'); + assert(signedActionTx.transaction.actions[1].account == 'eosio.token'); + assert(signedActionTx.transaction.actions[1].data != undefined); + assert(signedActionTx.transaction.actions[1].authorization != undefined); }); - it('Should sign the action without broadcasting it', async () => { + it('Should sign unique action ', async () => { const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); const tokensHolder = await eoslime.Account.createRandom(); const signer = await eoslime.Account.createRandom(); - const signedActionTx = await faucetContract.produce.sign(signer, tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'); - - assert(signedActionTx.signatures.length == 1); - assert(signedActionTx.transaction.expiration != undefined); - assert(signedActionTx.transaction.ref_block_num != undefined); - assert(signedActionTx.transaction.ref_block_prefix != undefined); - assert(signedActionTx.transaction.max_net_usage_words != undefined); - assert(signedActionTx.transaction.max_cpu_usage_ms != undefined); - assert(signedActionTx.transaction.delay_sec != undefined); - assert(signedActionTx.transaction.context_free_actions != undefined); - assert(signedActionTx.transaction.actions != undefined); - assert(signedActionTx.transaction.actions[0].name == 'produce'); - assert(signedActionTx.transaction.actions[0].account == faucetContract.name); - assert(signedActionTx.transaction.actions[0].data != undefined); - assert(signedActionTx.transaction.actions[0].authorization != undefined); + const signedActionTx = await faucetContract.actions.produce.sign([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { unique: true, from: signer }); + + assertSignedTransaction(signedActionTx, faucetContract.name); + assert(signedActionTx.transaction.actions[1].name == 'nonce'); + assert(signedActionTx.transaction.actions[1].account == 'eosio.null'); + assert(signedActionTx.transaction.actions[1].data != undefined); + assert(signedActionTx.transaction.actions[1].authorization != undefined); }); it('Should throw if trying to sign the action with an invalid signer', async () => { @@ -245,7 +295,7 @@ describe("Contract", function () { const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); const tokensHolder = await eoslime.Account.createRandom(); - await faucetContract.produce.sign('Fake signer', tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'); + await faucetContract.actions.produce.sign([tokensHolder.name, '100.0000 TKNS', tokenContract.name, 'memo'], { from: 'Fake signer' }); } catch (error) { assert(error.message.includes('String is not an instance of BaseAccount')); } @@ -255,51 +305,51 @@ describe("Contract", function () { describe("Blockchain tables", function () { it("Should have a default table getter", async () => { - const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name); // withdrawers is a table in the contract - assert(faucetContract.withdrawers); + assert(faucetContract.tables.withdrawers); }); it("Should apply the default query params if none provided", async () => { - const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name); const tokensHolder = await eoslime.Account.createRandom(); // faucetAccount is the executor - await faucetContract.produce(tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"]); - const allWithdrawers = await faucetContract.withdrawers.find(); + const allWithdrawers = await faucetContract.tables.withdrawers.find(); assert(allWithdrawers[0].quantity == PRODUCED_TOKENS_AMOUNT); assert(allWithdrawers[0].token_name == tokenContract.name); }); it("Should query a table", async () => { - const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name, faucetAccount); + const faucetContract = eoslime.Contract.fromFile(FAUCET_ABI_PATH, faucetAccount.name); const tokensHolder = await eoslime.Account.createRandom(); // faucetAccount is the executor - await faucetContract.produce(tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, "100.0000 TKNS", tokenContract.name, "memo"]); // With equal criteria - const equalResult = await faucetContract.withdrawers.equal(tokensHolder.name).find(); + const equalResult = await faucetContract.tables.withdrawers.equal(tokensHolder.name).find(); assert(equalResult[0].quantity == PRODUCED_TOKENS_AMOUNT); assert(equalResult[0].token_name == tokenContract.name); // With range criteria - const rangeResult = await faucetContract.withdrawers.range(0, 100 * TOKEN_PRECISION).index(2).find(); + const rangeResult = await faucetContract.tables.withdrawers.range(0, 100 * TOKEN_PRECISION).index(2).find(); assert(rangeResult[0].quantity == PRODUCED_TOKENS_AMOUNT); assert(rangeResult[0].token_name == tokenContract.name); // With limit // There is only one withdrawer - const allWithdrawers = await faucetContract.withdrawers.limit(10).find(); + const allWithdrawers = await faucetContract.tables.withdrawers.limit(10).find(); assert(allWithdrawers.length == 1); assert(allWithdrawers[0].quantity == PRODUCED_TOKENS_AMOUNT); assert(allWithdrawers[0].token_name == tokenContract.name); // With different index (By Balance) - const balanceWithdrawers = await faucetContract.withdrawers.equal(100 * TOKEN_PRECISION).index(2).find(); + const balanceWithdrawers = await faucetContract.tables.withdrawers.equal(100 * TOKEN_PRECISION).index(2).find(); assert(balanceWithdrawers[0].quantity == PRODUCED_TOKENS_AMOUNT); assert(balanceWithdrawers[0].token_name == tokenContract.name); }); @@ -311,13 +361,13 @@ describe("Contract", function () { await faucetContract.makeInline(); const tokensHolder = await eoslime.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"]); const tokensHolderBeforeBalance = await tokensHolder.getBalance("TKNS", tokenContract.name); assert(tokensHolderBeforeBalance.length == 0); // withdraw method behind the scene calls token's contract issue method - await faucetContract.withdraw(tokensHolder.name); + await faucetContract.actions.withdraw([tokensHolder.name]); const tokensHolderAfterBalance = await tokensHolder.getBalance("TKNS", tokenContract.name); assert(tokensHolderAfterBalance[0] == PRODUCED_TOKENS_AMOUNT); diff --git a/tests/helpers-tests.js b/tests/helpers-tests.js index 45dedde..b81c3a6 100644 --- a/tests/helpers-tests.js +++ b/tests/helpers-tests.js @@ -1,4 +1,5 @@ const assert = require('assert'); + const is = require('./../src/helpers/is'); const crypto = require('./../src/helpers/crypto'); const EventClass = require('./../src/helpers/event-class'); @@ -13,32 +14,31 @@ describe('Helpers scripts', function () { const expectedHash = 'a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e'; const resultedHash = crypto.hash('Hello World'); - assert(expectedHash == resultedHash) + assert(expectedHash == resultedHash); }); - it('Should encrypt and decrypt a data', async () => { + it('Should encrypt and decrypt data', async () => { const encryptedData = crypto.encrypt('Hello World', '123'); const decryptedData = crypto.decrypt(encryptedData, '123'); - assert(decryptedData == 'Hello World') + assert(decryptedData == 'Hello World'); }); - it('Should throw if it is not able to encrypt a data', async () => { + + it('Should throw if it is not able to encrypt data', async () => { try { crypto.encrypt('Hello World', { fake: 'FAKE' }); - assert(false); } catch (error) { - assert(error.message.includes('Couldn\'t encrypt the data')) + assert(error.message.includes('Couldn\'t encrypt the data')); } }); - it('Should throw if it is not able to decrypt a data', async () => { + it('Should throw if it is not able to decrypt data', async () => { try { const encryptedData = crypto.encrypt('Hello World', '123'); crypto.decrypt(encryptedData, { fake: 'FAKE' }); - assert(false); } catch (error) { - assert(error.message.includes('Couldn\'t decrypt the data')) + assert(error.message.includes('Couldn\'t decrypt the data')); } }); }); @@ -58,8 +58,24 @@ describe('Helpers scripts', function () { eventClass.on('created', () => x++); eventClass.emit('created'); - assert(x == 2) + assert(x == 2); }); + + it('Should be able for more than one subscriber to listen for an event', async () => { + const eventClass = new EventClass({ 'created': 'created' }); + eventClass.on('created', () => { }); + eventClass.on('created', () => { }); + + assert(eventClass.eventsHooks['created'].length == 2); + }); + + it('Should not be able to subscribe for unknown event', async () => { + const eventClass = new EventClass({}); + eventClass.on('created', () => { }); + + assert(eventClass.eventsHooks['created'] == undefined); + }); + }); describe('is.js', function () { diff --git a/tests/multisig-account-test.js b/tests/multisig-account-test.js index 59c7d2a..35a1cb5 100644 --- a/tests/multisig-account-test.js +++ b/tests/multisig-account-test.js @@ -27,11 +27,11 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); - await multiSigAccount.approve(multiSigAccount.accounts[0].publicKey, proposalId); + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + await multiSigAccount.approve(multiSigAccount.accounts[1].publicKey, proposalId); await multiSigAccount.processProposal(proposalId); - const withdrawers = (await faucetContract.withdrawers.find())[0]; + const withdrawers = (await faucetContract.tables.withdrawers.find())[0]; assert(eoslime.utils.toName(withdrawers.account) == account.name); assert(withdrawers.quantity == "100.0000 TKNS"); }); @@ -44,18 +44,18 @@ describe('Multi signature account', function () { await eoslime.utils.generateKeys() ] - await account.addAuthorityKey(keys[0].publicKey) - await account.addAuthorityKey(keys[1].publicKey) + await account.addOnBehalfKey(keys[0].publicKey) + await account.addOnBehalfKey(keys[1].publicKey) await account.increaseThreshold(2); const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadKeys(keys.map((key) => { return key.privateKey })); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [multiSigAccount.name, "100.0000 TKNS", multiSigAccount.name, "memo"]) + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [multiSigAccount.name, "100.0000 TKNS", multiSigAccount.name, "memo"]) await multiSigAccount.approve(multiSigAccount.accounts[1].publicKey, proposalId) await multiSigAccount.processProposal(proposalId); - const withdrawers = (await faucetContract.withdrawers.find())[0]; + const withdrawers = (await faucetContract.tables.withdrawers.find())[0]; assert(eoslime.utils.toName(withdrawers.account) == account.name); assert(withdrawers.quantity == "100.0000 TKNS"); }); @@ -71,7 +71,7 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); for (let i = 0; i < accounts.length; i++) { await multiSigAccount.approve(accounts[i].publicKey, proposalId); } @@ -80,7 +80,7 @@ describe('Multi signature account', function () { await multiSigAccount.processProposal(proposalId); - const withdrawers = (await faucetContract.withdrawers.find())[0]; + const withdrawers = (await faucetContract.tables.withdrawers.find())[0]; assert(eoslime.utils.toName(withdrawers.account) == account.name); assert(withdrawers.quantity == "100.0000 TKNS"); }); @@ -119,7 +119,7 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); await multiSigAccount.approve('Fake account', proposalId); assert(false); @@ -140,8 +140,8 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); - await multiSigAccount.approve(multiSigAccount.accounts[0].publicKey, 'Fake proposal'); + await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + await multiSigAccount.approve(multiSigAccount.accounts[1].publicKey, 'Fake proposal'); assert(false); } catch (error) { @@ -164,8 +164,8 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); - await multiSigAccount.approve(multiSigAccount.accounts[0].publicKey, proposalId); + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + await multiSigAccount.approve(multiSigAccount.accounts[1].publicKey, proposalId); await multiSigAccount.processProposal('Fake proposal'); @@ -187,7 +187,7 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); for (let i = 0; i < accounts.length; i++) { await multiSigAccount.approve(accounts[i].publicKey, proposalId); } @@ -212,7 +212,7 @@ describe('Multi signature account', function () { const multiSigAccount = eoslime.MultiSigAccount.load(account.name, account.privateKey); multiSigAccount.loadAccounts(accounts); - const proposalId = await multiSigAccount.propose(faucetContract.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); + const proposalId = await multiSigAccount.propose(faucetContract.actions.produce, [account.name, "100.0000 TKNS", account.name, "memo"]); await multiSigAccount.processProposal(proposalId); assert(false); diff --git a/tests/providers-tests.js b/tests/providers-tests.js index dc6641e..a8d4b91 100644 --- a/tests/providers-tests.js +++ b/tests/providers-tests.js @@ -27,6 +27,11 @@ const Networks = { url: 'https://jungle2.cryptolions.io', chainId: 'e70aaab8997e1dfce58fbfac80cbbb8fecec7b99cf982a9444273cbc64c41473' }, + jungle3: { + name: 'jungle3', + url: 'https://jungle3.cryptolions.io', + chainId: '2a02a0053e5a8cf73a56ba0fda11e4d92e0238a4a2aa74fccf46d5a910746840' + }, main: { name: 'main', url: 'https://eos.greymass.com', @@ -34,7 +39,7 @@ const Networks = { }, kylin: { name: 'kylin', - url: 'https://kylin.eoscanada.com', + url: 'https://api.kylin.alohaeos.com', chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191' }, custom: { @@ -66,6 +71,10 @@ describe('Providers', function () { const jungleProvider = eoslime.init('jungle').Provider; assert(JSON.stringify(jungleProvider.network) == JSON.stringify(Networks.jungle)); + // Jungle3 + const jungle3Provider = eoslime.init('jungle3').Provider; + assert(JSON.stringify(jungle3Provider.network) == JSON.stringify(Networks.jungle3)); + // Worbli const worbliProvider = eoslime.init('worbli').Provider; assert(JSON.stringify(worbliProvider.network) == JSON.stringify(Networks.worbli)); @@ -101,6 +110,12 @@ describe('Providers', function () { assert(JSON.stringify(jungleProvider.network.url) == JSON.stringify(Networks.jungle.url)); assert(jungleProvider.network.chainId == Networks.custom.chainId); + // Jungle3 + const jungle3Provider = eoslime.init('jungle3', { chainId: Networks.custom.chainId }).Provider; + assert(JSON.stringify(jungle3Provider.network.name) == JSON.stringify(Networks.jungle3.name)); + assert(JSON.stringify(jungle3Provider.network.url) == JSON.stringify(Networks.jungle3.url)); + assert(jungle3Provider.network.chainId == Networks.custom.chainId); + // Worbli const worbliProvider = eoslime.init('worbli', { url: Networks.custom.url }).Provider; assert(JSON.stringify(worbliProvider.network.name) == JSON.stringify(Networks.worbli.name)); @@ -147,6 +162,10 @@ describe('Providers', function () { const jungleProvider = new eoslimeInstance.Provider('jungle'); assert(JSON.stringify(jungleProvider.network) == JSON.stringify(Networks.jungle)); + // Jungle3 + const jungle3Provider = new eoslimeInstance.Provider('jungle3'); + assert(JSON.stringify(jungle3Provider.network) == JSON.stringify(Networks.jungle3)); + // Worbli const worbliProvider = new eoslimeInstance.Provider('worbli'); assert(JSON.stringify(worbliProvider.network) == JSON.stringify(Networks.worbli)); @@ -183,6 +202,12 @@ describe('Providers', function () { assert(JSON.stringify(jungleProvider.network.url) == JSON.stringify(Networks.jungle.url)); assert(jungleProvider.network.chainId == Networks.custom.chainId); + // Jungle3 + const jungle3Provider = new eoslimeInstance.Provider('jungle3', { chainId: Networks.custom.chainId }); + assert(JSON.stringify(jungle3Provider.network.name) == JSON.stringify(Networks.jungle3.name)); + assert(JSON.stringify(jungle3Provider.network.url) == JSON.stringify(Networks.jungle3.url)); + assert(jungle3Provider.network.chainId == Networks.custom.chainId); + // Worbli const worbliProvider = new eoslimeInstance.Provider('worbli', { url: Networks.custom.url }); assert(JSON.stringify(worbliProvider.network.name) == JSON.stringify(Networks.worbli.name)); @@ -240,9 +265,9 @@ describe('Providers', function () { const tokenContract = await eoslimeInstance.Contract.deploy(TOKEN_WASM_PATH, TOKEN_ABI_PATH); const faucetContract = await eoslimeInstance.Contract.deploy(FAUCET_WASM_PATH, FAUCET_ABI_PATH); - await tokenContract.create(faucetContract.name, TOTAL_SUPPLY); + await tokenContract.actions.create([faucetContract.name, TOTAL_SUPPLY]); const tokensHolder = await eoslimeInstance.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"]); // With equal criteria const equalResult = await Provider.select('withdrawers').from(faucetContract.name).equal(tokensHolder.name).find(); @@ -281,9 +306,9 @@ describe('Providers', function () { const tokenContract = await eoslimeInstance.Contract.deploy(TOKEN_WASM_PATH, TOKEN_ABI_PATH); const faucetContract = await eoslimeInstance.Contract.deploy(FAUCET_WASM_PATH, FAUCET_ABI_PATH); - await tokenContract.create(faucetContract.name, TOTAL_SUPPLY); + await tokenContract.actions.create([faucetContract.name, TOTAL_SUPPLY]); const tokensHolder = await eoslimeInstance.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"]); await Provider.select().find(); } catch (error) { @@ -299,9 +324,9 @@ describe('Providers', function () { const tokenContract = await eoslimeInstance.Contract.deploy(TOKEN_WASM_PATH, TOKEN_ABI_PATH); const faucetContract = await eoslimeInstance.Contract.deploy(FAUCET_WASM_PATH, FAUCET_ABI_PATH); - await tokenContract.create(faucetContract.name, TOTAL_SUPPLY); + await tokenContract.actions.create([faucetContract.name, TOTAL_SUPPLY]); const tokensHolder = await eoslimeInstance.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"]); await Provider.select('withdrawers').from().find(); } catch (error) { @@ -317,9 +342,9 @@ describe('Providers', function () { const tokenContract = await eoslimeInstance.Contract.deploy(TOKEN_WASM_PATH, TOKEN_ABI_PATH); const faucetContract = await eoslimeInstance.Contract.deploy(FAUCET_WASM_PATH, FAUCET_ABI_PATH); - await tokenContract.create(faucetContract.name, TOTAL_SUPPLY); + await tokenContract.actions.create([faucetContract.name, TOTAL_SUPPLY]); const tokensHolder = await eoslimeInstance.Account.createRandom(); - await faucetContract.produce(tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"); + await faucetContract.actions.produce([tokensHolder.name, PRODUCED_TOKENS_AMOUNT, tokenContract.name, "memo"]); await Provider.select('withdrawers').from(faucetContract.name).scope().find(); } catch (error) { diff --git a/tests/testing-contracts/compiled/faucet.abi b/tests/testing-contracts/compiled/faucet.abi index a4c8c59..02cd667 100644 --- a/tests/testing-contracts/compiled/faucet.abi +++ b/tests/testing-contracts/compiled/faucet.abi @@ -25,6 +25,11 @@ } ] }, + { + "name": "test", + "base": "", + "fields": [] + }, { "name": "withdraw", "base": "", @@ -64,6 +69,11 @@ "type": "produce", "ricardian_contract": "" }, + { + "name": "test", + "type": "test", + "ricardian_contract": "" + }, { "name": "withdraw", "type": "withdraw", diff --git a/tests/testing-contracts/compiled/faucet.wasm b/tests/testing-contracts/compiled/faucet.wasm index a6c4179..5d5fd64 100755 Binary files a/tests/testing-contracts/compiled/faucet.wasm and b/tests/testing-contracts/compiled/faucet.wasm differ diff --git a/tests/testing-contracts/faucet.cpp b/tests/testing-contracts/faucet.cpp index 255d15e..dc1577e 100644 --- a/tests/testing-contracts/faucet.cpp +++ b/tests/testing-contracts/faucet.cpp @@ -60,4 +60,6 @@ CONTRACT faucet : public contract } } } + + ACTION test() {} }; \ No newline at end of file diff --git a/tests/types/account.ts b/tests/types/account.ts new file mode 100644 index 0000000..cb11551 --- /dev/null +++ b/tests/types/account.ts @@ -0,0 +1,387 @@ +import assert from 'assert'; +import { it } from 'mocha'; + +import { init, utils } from '../../'; +const eoslime = init(); + +import { + Contract, + Account, + MultiSignatureAccount +} from '../../types'; + +import { assertTransactionResult } from './utils'; + +describe.only('All types of accounts', function () { + + this.timeout(20000); + + const ACCOUNT_NAME = 'eosio'; + const ACCOUNT_PRIVATE_KEY = '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3'; + + const ABI_PATH = "./tests/testing-contracts/compiled/faucet.abi"; + const WASM_PATH = "./tests/testing-contracts/compiled/faucet.wasm"; + + describe('Normal Account', function () { + + function assertAccount (account: Account) { + assert(account.name !== undefined); + assert(account.provider !== undefined); + assert(account.publicKey !== undefined); + assert(account.privateKey !== undefined); + assert(account.authority.actor !== undefined); + assert(account.authority.permission !== undefined); + + assert(typeof (account.send) == 'function'); + assert(typeof (account.buyRam) == 'function'); + assert(typeof (account.setWeight) == 'function'); + assert(typeof (account.getBalance) == 'function'); + assert(typeof (account.buyBandwidth) == 'function'); + assert(typeof (account.addAuthority) == 'function'); + assert(typeof (account.addPermission) == 'function'); + assert(typeof (account.addOnBehalfKey) == 'function'); + assert(typeof (account.getAuthorityInfo) == 'function'); + assert(typeof (account.increaseThreshold) == 'function'); + assert(typeof (account.addOnBehalfAccount) == 'function'); + assert(typeof (account.setAuthorityAbilities) == 'function'); + } + + describe('Static functions', function () { + it('Should load account', async () => { + const account = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + assertAccount(account); + }); + + it('Should create account [account creator]', async () => { + const creatorAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + + const accountName = await utils.randomName(); + const privateKey = await utils.randomPrivateKey(); + + const account = await eoslime.Account.create(accountName, privateKey, creatorAccount); + assertAccount(account); + }); + + it('Should create account [default account]', async () => { + const accountName = await utils.randomName(); + const privateKey = await utils.randomPrivateKey(); + + const account = await eoslime.Account.create(accountName, privateKey); + assertAccount(account); + }); + + it('Should create account from name [account creator]', async () => { + const creatorAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + + const accountName = await utils.randomName(); + const account = await eoslime.Account.createFromName(accountName, creatorAccount); + assertAccount(account); + }); + + it('Should create account from name [default account]', async () => { + const accountName = await utils.randomName(); + const account = await eoslime.Account.createFromName(accountName); + + assertAccount(account); + }); + + it('Should create random account [account creator]', async () => { + const creatorAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const account = await eoslime.Account.createRandom(creatorAccount); + + assertAccount(account); + }); + + it('Should create random account [default account]', async () => { + const account = await eoslime.Account.createRandom(); + assertAccount(account); + }); + + it('Should create random accounts [account creator]', async () => { + const creatorAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + + const accounts = await eoslime.Account.createRandoms(2, creatorAccount); + for (const account of accounts) { + assertAccount(account); + } + }); + + it('Should create random accounts [default account]', async () => { + const accounts = await eoslime.Account.createRandoms(2); + for (const account of accounts) { + assertAccount(account); + } + }); + + it('Should create encrypted account [account creator]', async () => { + const creatorAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const encryptedAccount = await eoslime.Account.createEncrypted('PASSWORD', creatorAccount); + + assert(encryptedAccount.network); + assert(encryptedAccount.cipherText); + assert(encryptedAccount.authority.actor); + assert(encryptedAccount.authority.permission); + }); + + it('Should create encrypted account [default account]', async () => { + const encryptedAccount = await eoslime.Account.createEncrypted('PASSWORD'); + + assert(encryptedAccount.network); + assert(encryptedAccount.cipherText); + assert(encryptedAccount.authority.actor); + assert(encryptedAccount.authority.permission); + }); + + it('Should convert encrypted account into account', async () => { + const encryptedAccount = await eoslime.Account.createEncrypted('PASSWORD'); + const decryptedAccount = eoslime.Account.fromEncrypted(encryptedAccount, 'PASSWORD'); + + assertAccount(decryptedAccount); + }); + }); + + describe('Main functions', function () { + + it('Should send EOS tokens', async () => { + const SEND_AMOUNT = '10.0000'; + const senderAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const receiverAccount = await eoslime.Account.createRandom(); + + const tx = await senderAccount.send(receiverAccount, SEND_AMOUNT, 'SYS'); + assertTransactionResult(tx); + }); + + it('Should buy ram [payer]', async () => { + const payer = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const account = await eoslime.Account.createRandom(); + + const tx = await account.buyRam(1000, payer); + assertTransactionResult(tx); + }); + + it('Should buy ram by self', async () => { + const eosAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const account = await eoslime.Account.createRandom(); + + await eosAccount.send(account, '10.0000', 'SYS'); + + const tx = await account.buyRam(1000); + assertTransactionResult(tx); + }); + + it('Should buy bandwidth [payer]', async () => { + const payer = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const account = await eoslime.Account.createRandom(); + + const tx = await account.buyBandwidth('10.0000 SYS', '10.0000 SYS', payer); + assertTransactionResult(tx); + }); + + it('Should buy bandwidth by self', async () => { + const eosAccount = eoslime.Account.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const account = await eoslime.Account.createRandom(); + + await eosAccount.send(account, '10.0000', 'SYS'); + + const tx = await account.buyBandwidth('10 SYS', '10 SYS'); + assertTransactionResult(tx); + }); + + it('Should add authority', async () => { + const account = await eoslime.Account.createRandom(); + const tx = await account.addAuthority('custom'); + assertTransactionResult(tx); + }); + + it('Should create permission for active authority', async () => { + const account = await eoslime.Account.createRandom(); + const tx = await account.addPermission('eosio.code'); + + assertTransactionResult(tx); + }); + + it('Should add permission with weight', async () => { + const accounts = await eoslime.Account.createRandom(); + const tx = await accounts.addPermission('eosio.code', 2); + + assertTransactionResult(tx); + }); + + it('Should allow another a keys pair to act on behalf', async () => { + const account = await eoslime.Account.createRandom(); + const keysPair = await utils.generateKeys(); + + const tx = await account.addOnBehalfKey(keysPair.publicKey); + assertTransactionResult(tx); + }); + + it('Should add keys pair with weight', async () => { + const WEIGHT = 2; + + const account = await eoslime.Account.createRandom(); + const keysPair = await utils.generateKeys(); + + const tx = await account.addOnBehalfKey(keysPair.publicKey, WEIGHT); + assertTransactionResult(tx); + }); + + it('Should allow another account to act on behalf', async () => { + const accounts = await eoslime.Account.createRandoms(2); + const child = accounts[0]; + const parent = accounts[1]; + + const tx = await child.addOnBehalfAccount(parent.name, 'active'); + assertTransactionResult(tx); + }); + + it('Should add authority account with weight', async () => { + const WEIGHT = 2; + + const accounts = await eoslime.Account.createRandoms(2); + const child = accounts[0]; + const parent = accounts[1]; + + const tx = await child.addOnBehalfAccount(parent.name, 'active', WEIGHT); + assertTransactionResult(tx); + }); + + it('Should increase authority threshold', async () => { + const THRESHOLD = 2; + const account = await eoslime.Account.createRandom(); + + const keysPair = await utils.generateKeys(); + await account.addOnBehalfKey(keysPair.publicKey); + const tx = await account.increaseThreshold(THRESHOLD); + + assertTransactionResult(tx); + }); + + it('Should set weight correctly authority threshold', async () => { + const account = await eoslime.Account.createRandom(); + + const WEIGHT = 2; + const tx = await account.setWeight(WEIGHT); + + assertTransactionResult(tx); + }); + + it('Should get authority details', async () => { + const account = await eoslime.Account.createRandom(); + const authorityInfo = await account.getAuthorityInfo(); + + assert(authorityInfo.perm_name) + assert(authorityInfo.parent) + assert(authorityInfo.required_auth.threshold) + assert(Array.isArray(authorityInfo.required_auth.keys)) + assert(Array.isArray(authorityInfo.required_auth.accounts)) + assert(Array.isArray(authorityInfo.required_auth.waits)) + }); + + it('Should get balance', async () => { + const account = await eoslime.Account.createRandom(); + const accBalance = await account.getBalance(); + + assert(accBalance.length == 0); + }); + }); + }); + + describe('Authority Account', function () { + it('Should set authority abilities', async () => { + const { name } = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + + const account = await eoslime.Account.createRandom(); + await account.addAuthority('custom'); + const tx = await account.setAuthorityAbilities('custom', [ + { + action: 'test', + contract: name + } + ]); + + assertTransactionResult(tx); + }); + }); + + describe('MultiSignature Account', function () { + function assertMultiSigAccount (account: MultiSignatureAccount) { + assert(account.name); + assert(account.accounts); + assert(account.provider); + assert(account.proposals); + assert(account.publicKey); + assert(account.privateKey); + assert(account.authority.actor); + assert(account.authority.permission); + + assert(typeof (account.loadKeys) == 'function'); + assert(typeof (account.loadAccounts) == 'function'); + + assert(typeof (account.propose) == 'function'); + assert(typeof (account.approve) == 'function'); + assert(typeof (account.processProposal) == 'function'); + } + + describe('Static functions', function () { + it('Should load multi signature account', async () => { + const account = eoslime.MultiSigAccount.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + assertMultiSigAccount(account); + }); + }); + + describe('Main functions', function () { + + let contract: Contract; + + // TODO: Implement stub version + beforeEach(async () => { + contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + }); + + it('Should load more keys', async () => { + const keys = [ + (await utils.generateKeys()).privateKey, + (await utils.generateKeys()).privateKey + ]; + + const multiSigAccount = eoslime.MultiSigAccount.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + multiSigAccount.loadKeys(keys); + }); + + it('Should load more accounts', async () => { + const accounts = await eoslime.Account.createRandoms(2); + + const multiSigAccount = eoslime.MultiSigAccount.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + multiSigAccount.loadAccounts(accounts); + }); + + it('Should propose a transaction to be broadcasted', async () => { + const multiSigAccount = eoslime.MultiSigAccount.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + const proposalId = await multiSigAccount.propose(contract.actions.test, []); + + assert(proposalId); + }); + + it('Should approve a transaction for broadcasting', async () => { + const accounts = await eoslime.Account.createRandoms(2); + const multiSigAccount = eoslime.MultiSigAccount.load(ACCOUNT_NAME, ACCOUNT_PRIVATE_KEY); + multiSigAccount.loadAccounts(accounts); + + const proposalId = await multiSigAccount.propose(contract.actions.test, []); + + await multiSigAccount.approve(multiSigAccount.accounts[0].publicKey, proposalId); + }); + + it('Should broadcast a proposed transaction', async () => { + const { name, privateKey } = await eoslime.Account.createRandom(); + + const multiSigAccount = eoslime.MultiSigAccount.load(name, privateKey); + const proposalId = await multiSigAccount.propose(contract.actions.test, []); + + const tx = await multiSigAccount.processProposal(proposalId); + assert(tx.processed !== undefined); + assert(tx.transaction_id !== undefined); + }); + }); + }); +}); diff --git a/tests/types/contract.ts b/tests/types/contract.ts new file mode 100644 index 0000000..492643e --- /dev/null +++ b/tests/types/contract.ts @@ -0,0 +1,215 @@ +import assert from 'assert'; +import { it } from 'mocha'; + +import { init } from '../../'; +const eoslime = init(); + +import { + Contract, + TransactionResult +} from '../../types'; + +import { assertRawTransaction, assertSignedAction, assertTransactionResult } from './utils'; + +const ABI_PATH = "./tests/testing-contracts/compiled/faucet.abi"; +const WASM_PATH = "./tests/testing-contracts/compiled/faucet.wasm"; + +describe("Contract", function () { + + this.timeout(20000); + + const eosio = eoslime.Account.load('eosio', '5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3'); + + const assertContract = function (contract: Contract): void { + assert(typeof contract.actions.produce == "function"); + assert(typeof contract.tables.withdrawers !== undefined); + + + assert(contract.abi !== undefined); + assert(contract.name !== undefined); + assert(contract.executor !== undefined); + } + + describe("Instantiation", function () { + it("Should have fromFile function", async () => { + const contract = eoslime.Contract.fromFile(ABI_PATH, 'contracttest', eosio); + assertContract(contract); + }); + + it("Should have at function", async () => { + const { name } = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const contract = await eoslime.Contract.at(name, eosio); + assertContract(contract); + }); + + it("Should set default eosio as executor if none is provided", async () => { + const contract = eoslime.Contract.fromFile(ABI_PATH, 'contracttest'); + assertContract(contract); + }); + }); + + describe("Deployment", function () { + it("Should have deploy function", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + assertContract(contract); + }); + + it("Should have deployOnAccount function", async () => { + const contractAccount = await eoslime.Account.createRandom() + const contract = await eoslime.Contract.deployOnAccount(WASM_PATH, ABI_PATH, contractAccount); + assertContract(contract); + }); + + it("Should have deployRaw function", async () => { + const contract_A = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const rawWASM = await contract_A.getRawWASM(); + + const contract = await eoslime.Contract.deployRaw(rawWASM, contract_A.abi); + assertContract(contract); + }); + + it("Should have deployRawOnAccount function", async () => { + const contract_A = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const rawWASM = await contract_A.getRawWASM(); + const contractAccount = await eoslime.Account.createRandom() + + const contract = await eoslime.Contract.deployRawOnAccount( + rawWASM, + contract_A.abi, + contractAccount + ); + assertContract(contract); + }); + }); + + describe("Blockchain actions", function () { + + it("Should have blockchain actions", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const tx = await contract.actions.test([]) + + assertTransactionResult(tx); + }); + + it("Should execute a blockchain action from another executor", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + await contract.actions.test([], { from: eosio }); + }); + + it('Should process nonce-action', async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + await contract.actions.test([], { unique: true }); + }); + + it('Should process token action', async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + await contract.actions.test([], { from: eosio, tokens: '5.0000 SYS' }); + }); + + describe("Methods", function () { + + it('Should get a raw transaction from an action', async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const rawActionTx = await contract.actions.test.getRawTransaction([]); + + assertRawTransaction(rawActionTx); + }); + + it('Should get a raw transaction from an action with options', async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const rawActionTx = await contract.actions.test.getRawTransaction([], { from: eosio }); + + assertRawTransaction(rawActionTx); + }); + + it('Should sign an action without broadcasting it', async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + const signedActionTx = await contract.actions.test.sign([]); + assertSignedAction(signedActionTx); + }); + + it('Should sign an action with options without broadcasting it', async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + + const signer = await eoslime.Account.createRandom(); + const signedActionTx = await contract.actions.test.sign([], { from: signer, unique: true }); + assertSignedAction(signedActionTx); + }); + }); + }); + + describe("Blockchain tables", function () { + + it("Should have a default table getter", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + assert(typeof contract.tables.withdrawers !== undefined); + }); + + it("Should have table query functions", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + + // With equal criteria + await contract.tables.withdrawers.equal('custom').find(); + + // With range criteria + await contract.tables.withdrawers.range(0, 1).find(); + + // With limit + await contract.tables.withdrawers.limit(10).find(); + + // With different index (By Balance) + await contract.tables.withdrawers.index(2).find(); + + // With scope + await contract.tables.withdrawers.scope(contract.name).find(); + }); + }); + + describe("Inline a contract", function () { + it("Should have makeInline function", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + + assert(typeof contract.makeInline == 'function') + await contract.makeInline(); + }); + }); + + describe("Retrieve raw WASM", function () { + it("Should have getRawWASM function", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + + assert(typeof contract.getRawWASM == 'function'); + await contract.getRawWASM(); + }); + }); + + describe("Events", function () { + it("Should have init event", async () => { + const { name } = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + + eoslime.Contract.on('init', (contract: Contract) => { assertContract(contract) }); + eoslime.Contract.fromFile(ABI_PATH, name, eosio); + }); + + it("Should have deploy event", async () => { + eoslime.Contract.on('deploy', (contract: Contract, deployTx) => { + assertContract(contract); + assert(deployTx.length == 2); + }); + + await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + }); + + describe("Blockchain action events", function () { + it("Should have getRawWASM function", async () => { + const contract = await eoslime.Contract.deploy(WASM_PATH, ABI_PATH); + contract.actions.test.on('processed', (txReceipt: TransactionResult, ...params: any[]) => { + assertTransactionResult(txReceipt); + assert(params[0] == contract.executor.name); + }); + + await contract.actions.withdraw([contract.executor.name]); + }); + }); + }); +}); diff --git a/tests/types/provider.ts b/tests/types/provider.ts new file mode 100644 index 0000000..1e7c3fc --- /dev/null +++ b/tests/types/provider.ts @@ -0,0 +1,279 @@ +import assert from 'assert'; +import { it } from 'mocha'; + +import { init } from '../../'; +const { Provider } = init(); + +describe('Provider', function () { + + const Networks = { + bos: { + name: 'bos', + url: 'https://hapi.bos.eosrio.io', + chainId: 'd5a3d18fbb3c084e3b1f3fa98c21014b5f3db536cc15d08f9f6479517c6a3d86' + }, + local: { + name: 'local', + url: 'http://127.0.0.1:8888', + chainId: 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f' + }, + worbli: { + name: 'main', + url: 'https://eos.greymass.com', + chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906' + }, + jungle: { + name: 'jungle', + url: 'https://jungle2.cryptolions.io', + chainId: 'e70aaab8997e1dfce58fbfac80cbbb8fecec7b99cf982a9444273cbc64c41473' + }, + main: { + name: 'main', + url: 'https://eos.greymass.com', + chainId: 'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906' + }, + kylin: { + name: 'kylin', + url: 'https://api.kylin.alohaeos.com', + chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191' + }, + custom: { + name: 'custom', + url: 'https://custom.com', + chainId: '123' + }, + } + + describe('Instantiation eoslime', function () { + it('Should instantiate with a correct Provider from connection', async () => { + // Local + const localProvider = init().Provider; + assert(JSON.stringify(localProvider.network) == JSON.stringify(Networks.local)); + + // Jungle + const jungleProvider = init('jungle').Provider; + assert(JSON.stringify(jungleProvider.network) == JSON.stringify(Networks.jungle)); + + // Worbli + const worbliProvider = init('worbli').Provider; + assert(JSON.stringify(worbliProvider.network) == JSON.stringify(Networks.worbli)); + + // Main + const mainProvider = init('main').Provider; + assert(JSON.stringify(mainProvider.network) == JSON.stringify(Networks.main)); + + // Bos + const bosProvider = init('bos').Provider; + assert(JSON.stringify(bosProvider.network) == JSON.stringify(Networks.bos)); + + // Kylin + const kylinProvider = init('kylin').Provider; + assert(JSON.stringify(kylinProvider.network) == JSON.stringify(Networks.kylin)); + + // Custom + const customProvider = init({ url: Networks.custom.url, chainId: Networks.custom.chainId }).Provider; + assert(JSON.stringify(customProvider.network) == JSON.stringify(Networks.custom)); + }); + + it('Should instantiate with a correct Provider from provided connection', async () => { + + // Local + const localProvider = init('local', { url: Networks.custom.url }).Provider; + assert(JSON.stringify(localProvider.network.name) == JSON.stringify(Networks.local.name)); + assert(JSON.stringify(localProvider.network.chainId) == JSON.stringify(Networks.local.chainId)); + assert(JSON.stringify(localProvider.network.url) == JSON.stringify(Networks.custom.url)); + + // Jungle + const jungleProvider = init('jungle', { chainId: Networks.custom.chainId }).Provider; + assert(JSON.stringify(jungleProvider.network.name) == JSON.stringify(Networks.jungle.name)); + assert(JSON.stringify(jungleProvider.network.url) == JSON.stringify(Networks.jungle.url)); + assert(jungleProvider.network.chainId == Networks.custom.chainId); + + // Worbli + const worbliProvider = init('worbli', { url: Networks.custom.url }).Provider; + assert(JSON.stringify(worbliProvider.network.name) == JSON.stringify(Networks.worbli.name)); + assert(JSON.stringify(worbliProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(worbliProvider.network.chainId) == JSON.stringify(Networks.worbli.chainId)); + + // Main + const mainProvider = init('main', { url: Networks.custom.url }).Provider; + assert(JSON.stringify(mainProvider.network.name) == JSON.stringify(Networks.main.name)); + assert(JSON.stringify(mainProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(mainProvider.network.chainId) == JSON.stringify(Networks.main.chainId)); + + // Bos + const bosProvider = init('bos', { url: Networks.custom.url }).Provider; + assert(JSON.stringify(bosProvider.network.name) == JSON.stringify(Networks.bos.name)); + assert(JSON.stringify(bosProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(bosProvider.network.chainId) == JSON.stringify(Networks.bos.chainId)); + + // Kylin + const kylinProvider = init('kylin', { url: Networks.custom.url }).Provider; + assert(JSON.stringify(kylinProvider.network.name) == JSON.stringify(Networks.kylin.name)); + assert(JSON.stringify(kylinProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(kylinProvider.network.chainId) == JSON.stringify(Networks.kylin.chainId)); + }); + }); + + describe('Create Provider', function () { + it('Should be able to create a new Provider with default connection', async () => { + // Local + const localProvider = new Provider('local'); + assert(JSON.stringify(localProvider.network) == JSON.stringify(Networks.local)); + + // Jungle + const jungleProvider = new Provider('jungle'); + assert(JSON.stringify(jungleProvider.network) == JSON.stringify(Networks.jungle)); + + // Worbli + const worbliProvider = new Provider('worbli'); + assert(JSON.stringify(worbliProvider.network) == JSON.stringify(Networks.worbli)); + + // Main + const mainProvider = new Provider('main'); + assert(JSON.stringify(mainProvider.network) == JSON.stringify(Networks.main)); + + // Bos + const bosProvider = new Provider('bos'); + assert(JSON.stringify(bosProvider.network) == JSON.stringify(Networks.bos)); + + // Kylin + const kylinProvider = new Provider('kylin'); + assert(JSON.stringify(kylinProvider.network) == JSON.stringify(Networks.kylin)); + + // Custom + const customProvider = new Provider({ url: Networks.custom.url, chainId: Networks.custom.chainId }); + assert(JSON.stringify(customProvider.network) == JSON.stringify(Networks.custom)); + }); + + it('Should be able to create a new Provider from connection', async () => { + // Local + const localProvider = new Provider('local', { url: Networks.custom.url }); + assert(JSON.stringify(localProvider.network.name) == JSON.stringify(Networks.local.name)); + assert(JSON.stringify(localProvider.network.chainId) == JSON.stringify(Networks.local.chainId)); + assert(JSON.stringify(localProvider.network.url) == JSON.stringify(Networks.custom.url)); + + // Jungle + const jungleProvider = new Provider('jungle', { chainId: Networks.custom.chainId }); + assert(JSON.stringify(jungleProvider.network.name) == JSON.stringify(Networks.jungle.name)); + assert(JSON.stringify(jungleProvider.network.url) == JSON.stringify(Networks.jungle.url)); + assert(jungleProvider.network.chainId == Networks.custom.chainId); + + // Worbli + const worbliProvider = new Provider('worbli', { url: Networks.custom.url }); + assert(JSON.stringify(worbliProvider.network.name) == JSON.stringify(Networks.worbli.name)); + assert(JSON.stringify(worbliProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(worbliProvider.network.chainId) == JSON.stringify(Networks.worbli.chainId)); + + // Main + const mainProvider = new Provider('main', { url: Networks.custom.url }); + assert(JSON.stringify(mainProvider.network.name) == JSON.stringify(Networks.main.name)); + assert(JSON.stringify(mainProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(mainProvider.network.chainId) == JSON.stringify(Networks.main.chainId)); + + // Bos + const bosProvider = new Provider('bos', { url: Networks.custom.url }); + assert(JSON.stringify(bosProvider.network.name) == JSON.stringify(Networks.bos.name)); + assert(JSON.stringify(bosProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(bosProvider.network.chainId) == JSON.stringify(Networks.bos.chainId)); + + // Kylin + const kylinProvider = new Provider('kylin', { url: Networks.custom.url }); + assert(JSON.stringify(kylinProvider.network.name) == JSON.stringify(Networks.kylin.name)); + assert(JSON.stringify(kylinProvider.network.url) == JSON.stringify(Networks.custom.url)); + assert(JSON.stringify(kylinProvider.network.chainId) == JSON.stringify(Networks.kylin.chainId)); + }); + }); + + describe('Reset provider', function () { + it('Should be able to reset the provider', async () => { + const jungleProvider = new Provider('jungle'); + Provider.reset(jungleProvider); + + assert(JSON.stringify(Provider.network) == JSON.stringify(Networks.jungle)); + }); + }); + + describe('Retrieve table [Table Reader]', function () { + + describe("Select Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table'); + assert(typeof query.from == 'function'); + }); + }); + + describe("From Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table').from('from'); + assert(typeof query.scope == 'function'); + assert(typeof query.equal == 'function'); + assert(typeof query.range == 'function'); + assert(typeof query.limit == 'function'); + assert(typeof query.index == 'function'); + assert(typeof query.find == 'function'); + }); + }); + + describe("Scope Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table').from('from').scope('scope'); + assert(typeof query.equal == 'function'); + assert(typeof query.range == 'function'); + assert(typeof query.limit == 'function'); + assert(typeof query.index == 'function'); + assert(typeof query.find == 'function'); + }); + }); + + describe("Equal Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table').from('from').scope('scope').equal('equal'); + assert(typeof query.limit == 'function'); + assert(typeof query.index == 'function'); + assert(typeof query.find == 'function'); + }); + }); + + describe("Range Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table').from('from').scope('scope').range(0, 1); + assert(typeof query.limit == 'function'); + assert(typeof query.index == 'function'); + assert(typeof query.find == 'function'); + }); + }); + + describe("Limit Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table').from('from').scope('scope').limit(10); + assert(typeof query.equal == 'function'); + assert(typeof query.range == 'function'); + assert(typeof query.index == 'function'); + assert(typeof query.find == 'function'); + }); + }); + + describe("Index Query", function () { + it("Should have correct chain of functions", async () => { + const query = Provider.select('table').from('from').scope('scope').index(2); + assert(typeof query.equal == 'function'); + assert(typeof query.range == 'function'); + assert(typeof query.limit == 'function'); + assert(typeof query.find == 'function'); + }); + }); + }); + + describe('Retrieve contract ABI', function () { + it('Should retrieve contract ABI', async () => { + await Provider.getABI('eosio'); + }); + }); + + describe('Retrieve contract raw WASM', function () { + it('Should retrieve contract raw WASM', async () => { + await Provider.getRawWASM('eosio'); + }); + }); +}); diff --git a/tests/types/utils/index.ts b/tests/types/utils/index.ts new file mode 100644 index 0000000..3a6e6a2 --- /dev/null +++ b/tests/types/utils/index.ts @@ -0,0 +1,38 @@ +import assert from 'assert'; + +import { TransactionResult, RawTransaction, SignedTransaction } from '../../../types'; + +export function assertRawTransaction (rawTransaction: RawTransaction): void { + assert(rawTransaction.actions !== undefined); + assert(rawTransaction.delay_sec !== undefined); + assert(rawTransaction.expiration !== undefined); + assert(rawTransaction.ref_block_num !== undefined); + assert(rawTransaction.max_cpu_usage_ms !== undefined); + assert(rawTransaction.ref_block_prefix !== undefined); + assert(rawTransaction.max_net_usage_words !== undefined); + assert(rawTransaction.context_free_actions !== undefined); +} + +export function assertSignedAction (signedTx: SignedTransaction): void { + assert(signedTx.compression !== undefined); + assert(signedTx.signatures.length > 0); + assertRawTransaction(signedTx.transaction); +} + +export function assertTransactionResult (txResult: TransactionResult): void { + assert(txResult.broadcast !== undefined); + assert(txResult.transaction_id !== undefined); + assert(txResult.processed.id !== undefined); + assert(txResult.processed.block_num !== undefined); + assert(txResult.processed.block_time !== undefined); + assert(txResult.processed.producer_block_id !== undefined); + assert(txResult.processed.receipt !== undefined); + assert(txResult.processed.elapsed !== undefined); + assert(txResult.processed.net_usage !== undefined); + assert(txResult.processed.scheduled !== undefined); + assert(txResult.processed.action_traces !== undefined); + assert(txResult.processed.account_ram_delta !== undefined); + assert(txResult.processed.except !== undefined); + assert(txResult.processed.error_code !== undefined); + assertSignedAction(txResult.transaction); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3092dbc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "suppressImplicitAnyIndexErrors": true, + "target": "es6", + "noImplicitAny": true, + "moduleResolution": "node", + "sourceMap": true, + "outDir": "dist", + "baseUrl": ".", + "paths": { + "*": [ + "node_modules/*", + "types/*" + ] + }, + "experimentalDecorators": true, + "emitDecoratorMetadata": true + }, + "include": [ + "./index.d.ts", + "./types/**/*" + ] +} \ No newline at end of file diff --git a/types/account/index.d.ts b/types/account/index.d.ts new file mode 100644 index 0000000..d937ac9 --- /dev/null +++ b/types/account/index.d.ts @@ -0,0 +1,274 @@ +import { BaseProvider } from '../provider'; +import { ContractFunction } from '../contract'; +import { TransactionResult, AuthorityDetails, BroadCastedTransaction } from '../miscellaneous'; + +interface ExecutiveAuthority { + actor: string, + permission: string; +} + +interface EncryptedAccount { + name: string; + network: string; + authority: ExecutiveAuthority; + cipherText: string; +} + +export class AccountFactory { + + constructor (provider: BaseProvider); + + /** + * + * @description Load existing network account + * @param {string} name Account name + * @param {string} privateKey Private key of the signer + * @param {string} [authorityName] Authority account will act from + * @returns {Account} Loaded account + */ + public load (name: string, privateKey: string, authorityName?: string): Account; + + /** + * @description Creates a fresh new account for a given name and private key + * + * @param {string} accountName Desired name of the created account + * @param {string} privateKey Desired private key of the created account + * @param {Account} [accountCreator] Another account responsible for paying creation fees + * @returns {Promise} Created account + */ + public create (accountName: string, privateKey: string, accountCreator?: Account): Promise; + + /** + * @description Creates a fresh new account for a given name + * + * @param {string} accountName Desired name of the created account + * @param {Account} [accountCreator] Another account responsible for paying creation fees + * @returns {Promise} Created account + */ + public createFromName (accountName: string, accountCreator?: Account): Promise; + + /** + * @description Create new random account + * + * @param {Account} [accountCreator] Another account responsible for paying creation fees + * @returns {Promise} Created account + */ + public createRandom (accountCreator?: Account): Promise; + + /** + * @description Create new random accounts + * + * @param {number} accountsCount Number of accounts to be created + * @param {Account} [accountCreator] Another account responsible for paying creation fees + * @returns {Promise>} Array of created account + */ + public createRandoms (accountsCount: number, accountCreator?: Account): Promise> + + /** + * @description Create a new random account and encrypt it. + * + * @param {string} password Password the account data will be encrypted with + * @param {Account} [accountCreator] Another account responsible for paying creation fees + * @returns {Promise} Encrypted account data in JSON + */ + public createEncrypted (password: string, accountCreator?: Account): Promise; + + /** + * @description Decrypt an encrypted account + * + * @param {EncryptedAccount} encryptedAccount JSON format of the encrypted account + * @param {string} password Password for decrypting + * @returns {Account} Decrypted account + */ + public fromEncrypted (encryptedAccount: EncryptedAccount, password: string): Account; +} + +declare class BaseAccount { + public name: string; + public publicKey: string; + public privateKey: string; + public provider: BaseProvider; + public authority: ExecutiveAuthority; + + constructor (name: string, privateKey: string, provider: BaseProvider, permission: string); + + /** + * @description Returns information for loaded authority + * + * @returns {Promise} + */ + public getAuthorityInfo (): Promise; +} + +interface AuthorityAbilities { + action: string; + contract: string; +} + +export class Account extends BaseAccount { + + constructor (name: string, privateKey: string, provider: BaseProvider, permission: string); + + /** + * @description Buy ram for this account + * + * @param {number} bytes Number of RAM bytes + * @param {Account} [payer] Another account responsible for paying + * @returns {Promise} Transaction receipt + */ + public buyRam (bytes: number, payer?: Account): Promise; + + /** + * @description Buy cpu and network for this account + * + * @param {string} cpu Amount of tokens one want to buy cpu for + * @param {string} net Amount of tokens one want to buy net for + * @param {Account} [payer] Another account responsible for paying + * @returns {Promise} Transaction receipt + */ + public buyBandwidth (cpu: string, net: string, payer?: Account): Promise; + + /** + * @description Send tokens to another account + * + * @param {Account} receiver Account tokens will be sent to + * @param {string} amount Tokens amount + * @param {string} symbol Token symbol + * @returns {Promise} Transaction receipt + */ + public send (receiver: Account, amount: string, symbol: string): Promise; + + /** + * @description Add sub authority to the current account's one + * + * @param {string} authorityName Desired name of the new authority + * @param {number} [threshold] Desired threshold of the new authority + * @returns {Promise} Transaction receipt + */ + public addAuthority (authorityName: string, threshold?: number): Promise; + + /** + * @description Define what actions of which contracts the authority has permissions to execute + * + * @param {string} authorityName Sub authority one is setting permissions for + * @param {Array} abilities Array of permissions + * @returns {Promise} Transaction receipt + */ + public setAuthorityAbilities (authorityName: string, abilities: Array): Promise; + + /** + * @description Increase authority's threshold + * + * @param {number} threshold Number of desired threshold + * @returns {Promise} Transaction receipt + */ + public increaseThreshold (threshold: number): Promise; + + /** + * @description Add permission to authority such as eosio.code + * + * @param {string} authorityName Permissions [eosio.code] + * @param {number} [weight] Weight of the permission + * @returns {Promise} Transaction receipt + */ + public addPermission (authorityName: string, weight?: number): Promise; + + /** + * @description Allow another account to act from your account's authority + * + * @param {string} accountName Name of another account + * @param {string} [authority] Authority of another account + * @param {number} [weight] Weight another account has + * @returns {Promise} Transaction receipt + */ + public addOnBehalfAccount (accountName: string, authority?: string, weight?: number): Promise; + + /** + * @description Allow more keys to sign transactions from your account + * + * @param {string} publicKey Allowed public key + * @param {number} [weight] Weight the key has + * @returns {Promise} Transaction receipt + */ + public addOnBehalfKey (publicKey: string, weight?: number): Promise; + + /** + * @description Set weight on account public key. + * + * @param {number} weight Number of weight + * @returns {Promise} Transaction receipt + */ + public setWeight (weight: number): Promise; + + /** + * @description Returns the balance of provided token account has + * + * @param {string} [symbol] Token symbol + * @param {string} [code] Token contract name + * @returns {Promise>} Balance + */ + public getBalance (symbol?: string, code?: string): Promise>; +} + +export class MultiSignatureFactory { + + constructor (provider: BaseProvider); + + /** + * @description Load existing network multisig account + * + * @param {string} name Account name + * @param {string} privateKey Private key of one of the multisig owners + * @param {string} [authorityName] Authority the multisig will act from + * @returns {MultiSignatureAccount} Loaded multisig account + */ + public load (name: string, privateKey: string, authorityName?: string): MultiSignatureAccount; +} + +export class MultiSignatureAccount extends BaseAccount { + + public accounts: Array; + public proposals: Array; + + constructor (name: string, privateKey: string, provider: BaseProvider, authority: string); + + /** + * @description Load the private keys of the authority public keys in order to approve transactions with them + * + * @param {Array} privateKeys Private keys one will use to approve transactions with + */ + public loadKeys (privateKeys: Array): void; + + /** + * @description Load the accounts configured to act on behalf of the multisig authority + * + * @param {Array} accounts Accounts one will use to approve transactions with + */ + public loadAccounts (accounts: Array): void; + + /** + * @description Propose a transaction to be executed + * + * @param {ContractFunction} contractAction Action one propose to be broadcasted + * @param {Array} actionData Action data + * @returns {Promise} Proposal id + */ + public propose (contractAction: ContractFunction, actionData: Array): Promise; + + /** + * @description Sign a proposed transaction + * + * @param {string} publicKey Key of the approver + * @param {number} proposalId ID of proposal + * @returns {Promise} + */ + public approve (publicKey: string, proposalId: number): Promise; + + /** + * @description Broadcast proposal in case of enough approvals + * + * @param {number} proposalId ID of proposal + * @returns {Promise} Transaction receipt + */ + public processProposal (proposalId: number): Promise; +} diff --git a/types/contract/index.d.ts b/types/contract/index.d.ts new file mode 100644 index 0000000..3cee94d --- /dev/null +++ b/types/contract/index.d.ts @@ -0,0 +1,164 @@ +import { FromQuery } from '../table-reader'; +import { BaseProvider } from '../provider'; +import { Account, BaseAccount } from '../account'; +import { TransactionResult, RawTransaction, SignedTransaction } from '../miscellaneous'; + +declare abstract class EventClass { + public events: Array; + public eventsHooks: Map void>; + + constructor (events: Array); + + public on (eventName: string, callback: (...params: any[]) => void): void; + public emit (eventName: string, ...params: any[]): void; +} + +declare class ContractInitializator extends EventClass { + + constructor (provider: BaseProvider); + + /** + * @description Instantiate a contract by retrieving the ABI from the chain + * + * @param {string} contractName Contract name of the contract one wants to instantiate + * @param {Account} [contractExecutorAccount] Account responsible for signing and broadcasting transactions + * @returns {Promise} Instantiated contract + */ + public at (contractName: string, contractExecutorAccount?: Account): Promise; + + /** + * @description Instantiate a contract by providing the ABI + * + * @param {*} abi Contract ABI + * @param {string} contractName Contract name of the contract one wants to instantiate + * @param {Account} [contractExecutorAccount] Account responsible for signing and broadcasting transactions + * @returns {Contract} Instantiated contract + */ + public fromFile (abi: any, contractName: string, contractExecutorAccount?: Account): Contract +} + + +interface DeployOptions { + inline: boolean; +} + +export class ContractFactory extends ContractInitializator { + + constructor (provider: BaseProvider); + + /* Overwrite EventClass */ + public on (eventName: 'init', callback: (contract: Contract) => void): void; + public on (eventName: 'deploy', callback: (contract: Contract, deployTx: [TransactionResult, TransactionResult]) => void): void; + + /* Own functions */ + + /** + * @description Deploy a new contract on a random generated account from ABI and WASM files + * + * @param {string} wasmPath Path to the contract WASM file + * @param {string} abiPath Path to the contract ABI file + * @param {DeployOptions} [options] + * @returns {Promise} Deployed contract + */ + public deploy (wasmPath: string, abiPath: string, options?: DeployOptions): Promise; + + /** + * @description Deploy a new contract on a random generated account from loaded ABI and WASM + * + * @param {string} wasm Contract WASM + * @param {*} abi Contract ABI + * @param {DeployOptions} [options] + * @returns {Promise} Deployed contract + */ + public deployRaw (wasm: string, abi: any, options?: DeployOptions): Promise; + + /** + * @description Deploy a new contract on the provided account from ABI and WASM files + * + * @param {string} wasmPath Path to the contract WASM file + * @param {string} abiPath Path to the contract ABI file + * @param {Account} contractAccount Account the contract will be deployed on + * @param {DeployOptions} [options] + * @returns {Promise} Deployed contract + */ + public deployOnAccount (wasmPath: string, abiPath: string, contractAccount: Account, options?: DeployOptions): Promise; + + /** + * @description Deploy a new contract on the provided account from loaded ABI and WASM + * + * @param {string} wasm Contract WASM + * @param {*} abi Contract ABI + * @param {Account} contractAccount Account the contract will be deployed on + * @param {DeployOptions} [options] + * @returns {Promise} Deployed contract + */ + public deployRawOnAccount (wasm: string, abi: any, contractAccount: Account, options?: DeployOptions): Promise; +} + +interface ContractFunctionOptions { + from?: BaseAccount; + unique?: boolean; + tokens?: string +} + +export interface ContractFunction extends EventClass { + + (params: any[], options?: ContractFunctionOptions): Promise; + + /* Overwrite EventClass */ + on (eventName: 'processed', callback: (txResult: TransactionResult, ...fnParams: any[]) => void): void; + + /* Own functions */ + + /** + * @description Construct raw transaction for an action + * + * @param {any[]} params Action arguments + * @param {ContractFunctionOptions} [options] + * @returns {Promise} Raw transaction + */ + getRawTransaction (params: any[], options?: ContractFunctionOptions): Promise; + + /** + * @description Sign action and return a ready-to-broadcast transaction + * + * @param {any[]} params Action arguments + * @param {ContractFunctionOptions} [options] + * @returns {Promise} Ready to broadcast transaction + */ + sign (params: any[], options?: ContractFunctionOptions): Promise; +} + +interface ContractFunctions { + [prop: string]: ContractFunction; +} + +interface ContractTables { + [prop: string]: FromQuery; +} + +export class Contract { + public abi: any; + public name: string; + public executor: BaseAccount; + public provider: BaseProvider; + + public tables: ContractTables; + public actions: ContractFunctions; + + constructor (provider: BaseProvider, abi: any, contractName: string, contractExecutorAccount: Account); + + /** + * @description Enable the contract to make inline actions + * + * @returns {Promise} void + */ + public makeInline (): Promise; + + /** + * @description Retrieve contract raw WASM + * + * @returns {Promise} Contract raw WASM + */ + public getRawWASM (): Promise; +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..6d339da --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,5 @@ +export * from './account'; +export * from './contract'; +export * from './provider'; +export * from './miscellaneous'; +export * from './utils'; diff --git a/types/miscellaneous/index.d.ts b/types/miscellaneous/index.d.ts new file mode 100644 index 0000000..3ac9b0c --- /dev/null +++ b/types/miscellaneous/index.d.ts @@ -0,0 +1,103 @@ +interface TxActionTrace { + action_ordinal: number; + creator_action_ordinal: number; + closest_unnotified_ancestor_action_ordinal: number; + receipt: TxReceipt; + receiver: string; + act: TxAction; + context_free: boolean; + elapsed: number; + console: string; + trx_id: string; + block_num: number; + block_time: string; + producer_block_id: string; + account_ram_deltas: Array; + except: string; + error_code: string; + inline_traces: TxActionTrace +} + +interface TxReceipt { + status: string; + cpu_usage_us: number; + net_usage_words: number; +} + +interface TxAction { + account: string; + name: string; + authorization: [ + { + actor: string; + permission: string; + } + ]; + data: string; +} + +export interface RawTransaction { + expiration: string; + ref_block_num: number; + ref_block_prefix: number; + max_net_usage_words: number; + max_cpu_usage_ms: number; + delay_sec: number; + context_free_actions: Array; + actions: Array; + transaction_extensions: Array; +} + +export interface SignedTransaction { + compression: string; + transaction: RawTransaction; + signatures: Array; +} + +export interface BroadCastedTransaction { + transaction_id: string; + processed: + { + id: string; + block_num: number; + block_time: string; + producer_block_id: string; + receipt: TxReceipt; + elapsed: number; + net_usage: number; + scheduled: boolean; + action_traces: Array; + account_ram_delta: string; + except: string; + error_code: string; + } +} + +export interface TransactionResult extends BroadCastedTransaction { + broadcast: boolean; + transaction: SignedTransaction; +} + +interface TxAuthorityKey { + key: string; + weight: number; +} + +interface AuthAccount { + permission: { + actor: string; + permission: string + }; + weight: number; +} + +export interface AuthorityDetails { + perm_name: string; + parent: string; + required_auth: { + threshold: number; + keys: Array; + accounts: Array; + waits: Array; + } +} \ No newline at end of file diff --git a/types/provider/index.d.ts b/types/provider/index.d.ts new file mode 100644 index 0000000..4bc7372 --- /dev/null +++ b/types/provider/index.d.ts @@ -0,0 +1,73 @@ +import { Account } from '../account'; +import { SelectQuery } from '../table-reader'; + +export interface NetworkDetails { + url?: string; + chainId?: string; +} + +interface NetworkData { + name: string; + url: string; + chainId: string; +} + +export class BaseProvider { + public eos: any; + public network: NetworkData; + public defaultAccount: Account; + + constructor (networkConfig: NetworkData); + + /** + * @description Table query chain + * + * @param {string} table Contract table one wants to read data from + * @returns {SelectQuery} Select query chain + */ + public select (table: string): SelectQuery; + + /** + * @description Retrieve contract ABI from the chain + * + * @param {string} contractName Name of the contract + * @returns {Promise} Contract ABI + */ + public getABI (contractName: string): Promise; + + /** + * @description Retrieve contract WASM from the chain. Useful for deploying another contract directly + * + * @param {string} contractName Name of the contract + * @returns {Promise} Contract WASM + */ + public getRawWASM (contractName: string): Promise; +} + +declare class ProviderFactory { + + private instance: Provider; + private __provider: BaseProvider; + + constructor (network: string, config: NetworkDetails); + + /** + * @description Reset provider to another one + * + * @param {BaseProvider} newProvider New provider the current one will be set to + */ + public reset (newProvider: BaseProvider): void; + + /** + * @description List of eoslime supported networks + * + * @returns {Array} Supported networks names + */ + public availableNetworks (): Array; +} + +export interface Provider extends BaseProvider, ProviderFactory { + new(network?: string, config?: NetworkDetails): BaseProvider; + new(config?: NetworkDetails): BaseProvider; +} + diff --git a/types/table-reader/index.d.ts b/types/table-reader/index.d.ts new file mode 100644 index 0000000..7cb5f71 --- /dev/null +++ b/types/table-reader/index.d.ts @@ -0,0 +1,52 @@ +export class SelectQuery { + from (contractName: string): FromQuery; +} + +export class FromQuery { + equal (value: any): EqualQuery; + limit (limit: number): LimitQuery; + scope (accountName: string): ScopeQuery; + range (minValue: any, maxValue: any): RangeQuery; + index (index: number, keyType?: string): IndexQuery; + + find (): Promise>; +} + +export class ScopeQuery { + equal (value: any): EqualQuery; + limit (limit: number): LimitQuery; + range (minValue: any, maxValue: any): RangeQuery; + index (index: number, keyType?: string): IndexQuery; + + find (): Promise>; +} + +export class EqualQuery { + limit (limit: number): LimitQuery; + index (index: number, keyType: string): IndexQuery; + + find (): Promise>; +} + +export class RangeQuery { + limit (limit: number): LimitQuery; + index (index: number, keyType: string): IndexQuery; + + find (): Promise>; +} + +export class LimitQuery { + equal (value: any): EqualQuery; + range (minValue: any, maxValue: any): RangeQuery; + index (index: number, keyType: string): IndexQuery; + + find (): Promise>; +} + +export class IndexQuery { + equal (value: any): EqualQuery; + limit (limit: number): LimitQuery; + range (minValue: any, maxValue: any): RangeQuery; + + find (): Promise>; +} \ No newline at end of file diff --git a/types/utils/index.d.ts b/types/utils/index.d.ts new file mode 100644 index 0000000..2bc90fc --- /dev/null +++ b/types/utils/index.d.ts @@ -0,0 +1,43 @@ +interface KeysPair { + publicKey: string; + privateKey: string; +} + +export interface utils { + /** + * @description Convert account name from uint64 to string + * + * @param {string} encodedName Uint64 format of account name + * @returns {string} String format of account name + */ + toName (encodedName: string): string; + + /** + * @description Generate a random account name + * + * @returns {Promise} Account name + */ + randomName (): Promise; + + /** + * @description Generate a random public/private keys pair + * + * @returns {Promise} Keys pair + */ + generateKeys (): Promise; + + /** + * @description Generate a random private key + * + * @returns {Promise} Random key + */ + randomPrivateKey (): Promise; + + /** + * @description Construct an account name from a private key. The name is constructed in a custom way, it is not related to the private key in any manner + * + * @param {string} privateKey Private key + * @returns {Promise} Account name + */ + nameFromPrivateKey (privateKey: string): Promise; +}